library(parallel)
library(readr)
library(dplyr)
library(lubridate)
library(ggplot2)
library(purrr)
library(Rcpp)
library(rgdal)
library(rgeos)
library(sp)
library(tibble)
library(leaflet)
library(stringr)
library(xts)
library(tidyr)
library(dygraphs)
library(DT)

Data

The NOAA data I found on AWS was for NEXRAD, containing an archive of Level II data from June 1991 to the present. The NOAA Severe Weather Data Inventory contains Level III product data on hail and storms.

To begin, I downloaded the NEXRAD hail signatures data. These hail csv files were downloaded and extracted to the folder data/nexrad/hail/csv. Then the first step is reading all of the files.

cl <- makeCluster(4)
invisible(clusterEvalQ(cl, { library(readr); library(dplyr); library(lubridate) }))
 
hail_data <- clusterApplyLB(
  cl, list.files("/data_shared/geospatial/nexrad/hail/csv", full.names = TRUE),
  function(path) {
    read_csv(path, skip = 3, 
             col_names = c("time", "lon", "lat", "wsr_id", "cell_id", "range", 
                           "azimuth", "sevprob", "prob", "maxsize"),
             col_types = cols(time = col_datetime(format = "%Y%m%d%H%M%S"))) %>%
      mutate(azimuth = azimuth %% 360,
             yr = year(time), 
             mon = month(time))
  }
)
 
stopCluster(cl)
 
hail_full <- bind_rows(hail_data)

Here is a sample of a few rows from the data.


set.seed(2606)
 
hail_full %>%
  sample_n(100) %>%
  select(-yr, -mon) %>%
  mutate(time = as.character(time)) %>%
  datatable(rownames = FALSE,
            options = list(scrollX = TRUE))


This page contains some documentation on NEXRAD Level III products. The following are definitions of the variables in the hail data set.

  • time: observation time (GMT)

  • lon: longitude

  • lat: latitude

  • wsr_id: site ID

  • cell_id: cell ID unique to radar site

  • range: distance from station (nautical miles)

  • azimuth: angle from station (degrees)

  • sevprob: probability of severe hail (percent)

  • prob: probabilty of hail (percent)

  • max_size: max size (inches)

Values of -999 appear to be missing values.

Station metadata

nexrad_stations <- read_fwf(
  "data/stations/nexrad-stations.txt", 
  fwf_positions(start = c(1, 10, 15, 21, 52, 73, 76, 107, 117, 127, 134, 140), 
                end = c(8, 13, 19, 50, 71, 74, 105, 115, 125, 132, 138, NA),
                col_names = c("ncdcid", "icao", "wban", "name", "country", "state", "county", 
                              "lat", "lon", "elevation", "tz", "station_type")), 
  skip = 2,
  col_types = "cccccccddddc"
) 
 
asos_stations <- read_fwf(
  "data/stations/asos-stations.txt",
  fwf_positions(start = c(1, 10, 16, 23, 28, 59, 90, 111, 114, 145, 155, 166, 173, 179),
                end = c(8, 14, 21, 26, 57, 88, 109, 112, 143, 153, 164, 171, 177, NA),
                col_names = c("ncdcid", "wban", "coopid", "call", "name", "alt_name", "country",
                              "state", "county", "lat", "lon", "elevation", "tz", "station_type")),
  skip = 2,
  col_types = "cccccccccddddc"
)
 
station_cnt <- count(hail_full, wsr_id)
 
non_nexrad <- station_cnt %>%
  anti_join(nexrad_stations, by = c("wsr_id" = "icao")) %>%
  mutate(call = str_sub(wsr_id, start = 2),
         call = recode(call, "ICH"  = "ICT", "IDS" = "IND", "LVE" = "CLE"))
 
hail_nexrad <- nexrad_stations %>% 
  semi_join(station_cnt, by = c("icao" = "wsr_id")) %>%
  select(wsr_id = icao, everything()) %>%
  mutate(source = "NEXRAD")
 
hail_asos <- asos_stations %>%
  inner_join(non_nexrad, by = "call") %>%
  select(wsr_id, ncdcid, wban, name, country, state, county, lat, lon, elevation, tz, station_type) %>%
  mutate(source = "TDWR")
 
andrews_afb <- tibble(wsr_id = "KADW", ncdcid = "20009472", wban = "13705", 
                      name = "CAMP SPRINGS ANDREWS AFB", country = "UNITED STATES", state = "MD",
                      county = "PRINCE GEORGE'S", lat = 38.81667, lon = -76.86667, elevation = 282, 
                      tz = -5, station_type = NA_character_, source = "OTHER")
 
all_stations <- bind_rows(hail_nexrad, hail_asos, andrews_afb)

The hail data contains observations from both NEXRAD stations and Terminal Doppler Weather Radar (TDWR) stations.

Here are the observation counts for the 202 stations appearining in the data.


datatable(station_cnt,
          rownames = FALSE,
          colnames = c("Observations" = "n"),
          options = list(scrollX = TRUE,
                         pageLength = 5,
                         dom = "lipt")) %>%
  formatCurrency("Observations", currency = "", digits = 0)


From the Historical Observing Metadata Repository, I downloaded metadata on the NEXRAD stations. There are 161 NEXRAD stations. For a handful of stations, shown in the following table, there existed no observations in the hail data.


nexrad_stations %>% 
  anti_join(station_cnt, by = c("icao" = "wsr_id")) %>%
  datatable(class = "cell-border",
            rownames = FALSE,
            options = list(scrollX = TRUE, 
                           ordering = FALSE,
                           autoWidth = TRUE,
                           dom = "t",
                           pageLength = nrow(.)))


For 45 TDWR stations, which are located at airports, I found metadata information from the Automated Surface Observing System (ASOS) station list at the Historical Observing Metadata Repository.

For three stations, shown in the following table, the call sign (which I used to join the ASOS stations to the hail stations) differed from the last three letters of the wsr_id value.


tribble(~city, ~wsr_id, ~call,
        "Wichita", "KICH", "ICT",
        "Indianapolis", "KIDS", "IND",
        "Cleveland", "KLVE", "CLE")


I presume the wsr_id for these stations differed because there already existed NEXRAD stations with these values (KICT, KIND, KCLE). A note released in 2008 when NOAA began disseminating TDWR radar products seems to confirm that these call signs and id values correspond with each other.

In addition to the 156 NEXRAD stations and 45 TDWR stations, there was one additional station with observations in the hail data that was neither in the NEXRAD or ASOS list: KADW at Andrews AFB.

Here is a table of metadata for all 202 stations with observations in the hail data.


all_stations %>%
  datatable(rownames = FALSE,
            options = list(scrollX = TRUE))


Basic data exploration

azimuth_cnt <- hail_full %>%
  count(azimuth)
 
range_cnt <- hail_full %>%
  count(range)

I downloaded the hail csv files from 1995 to 2015. There are a total of 172,174,993 observations in the data.

Azimuth frequency

Here is a frequency polygon of azimuth in the data.

ggplot(azimuth_cnt) + 
  geom_freqpoly(aes(x = azimuth, y = n), stat = "identity") +
  expand_limits(y = 0) + 
  theme_minimal() +
  theme(axis.title.y = element_blank())

Range frequency

Here is a frequency polygon for the range variable.

ggplot(range_cnt) + 
  geom_freqpoly(aes(x = range, y = n), stat = "identity") +
  expand_limits(y = 0) + 
  theme_minimal() +
  theme(axis.title.y = element_blank())

We see that the overwhelming majority of ranges are less than, say, 250 nautical miles. However, there are a handful of seemingly extreme outliers. The maximum value of range is 998, and there are 3,651 observations with range greater than 250. Here is a plot of the frequency when we exclude ranges greater than 250.

ggplot(range_cnt %>%
         filter(range <= 250)) + 
  geom_freqpoly(aes(x = range, y = n), stat = "identity") +
  expand_limits(y = 0) + 
  theme_minimal() +
  theme(axis.title.y = element_blank())

The pattern is generally what we would expect, with an increase in frequency as range increases, as there is more available area for observations, then a decrease as range continues to increase beyond a certain point, as it starts to become less likely for the storm to be detected by the radar station.

There are a couple of peculiar blips in the data, such as the large spike at around 110 nautical miles.

range_cnt_by_source <- hail_full %>% 
  select(wsr_id, range) %>%
  left_join(all_stations %>%
              select(wsr_id, source),
            by = "wsr_id") %>%
  count(source, range) %>%
  ungroup
 
azimuth_cnt_by_source <- hail_full %>% 
  select(wsr_id, azimuth) %>%
  left_join(all_stations %>%
              select(wsr_id, source),
            by = "wsr_id") %>%
  count(source, azimuth) %>%
  ungroup

Let’s take a look at the distribution of range for NEXRAD vs. TDWR stations. Once again, we’ll filter to range of less than or equal to 250 nautical miles.

ggplot(range_cnt_by_source %>% 
         filter(source %in% c("NEXRAD", "TDWR"), range <= 250) %>% 
         group_by(source) %>% 
         mutate(rel_freq = n / sum(n)) %>% 
         ungroup) + 
  geom_freqpoly(aes(x = range, y = rel_freq), stat = "identity") +
  facet_wrap(~ source, ncol = 2) +
  expand_limits(y = 0) + 
  theme_minimal() +
  theme(axis.title = element_blank(),
        axis.ticks.y = element_blank(),
        axis.text.y = element_blank())

We can see that the TDWR stations have a much more limited range than the NEXRAD stations, with the NEXRAD range appearing to fall off a cliff at around 50 nautical miles.

Frequency over time

month_cnt <- hail_full %>% 
  count(wsr_id, yr, mon) %>% 
  ungroup %>%
  mutate(yrmon = as.yearmon(paste(yr, str_pad(mon, width = 2, side = "left", pad = "0"), sep = "-")))

The following chart shows the number of observations by month for the duration of the data set.


month_cnt %>% 
  group_by(yrmon) %>% 
  tally(n) %>%
  { xts(select(., nn), order.by = .[["yrmon"]]) } %>%
  dygraph %>%
  dyOptions(maxNumberWidth = 20) %>%
  dySeries("nn", label = "Observations") %>%
  dyAxis("y", pixelsPerLabel = 100,
         valueFormatter = 'function(d){return d.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ",");}',
         axisLabelFormatter = 'function(d){return d.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ",");}',
         axisLabelWidth = 70)


We see an increase in th early to mid 2000s, perhaps as more stations became active. We also notice the number of observations was quite depressed in 2002.

Here is a chart of the breakdown in observation counts between NEXRAD and TDWR stations.


month_cnt %>% 
  left_join(select(all_stations, wsr_id, source), by = "wsr_id") %>%
  group_by(source, yrmon) %>% 
  tally(n) %>%
  ungroup %>%
  spread(source, nn) %>%
  { xts(select(., NEXRAD, TDWR), order.by = .[["yrmon"]]) } %>%
  dygraph %>%
  dyOptions(maxNumberWidth = 20) %>%
  dyAxis("y", pixelsPerLabel = 100,
         valueFormatter = 'function(d){return d.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ",");}',
         axisLabelFormatter = 'function(d){return d.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ",");}',
         axisLabelWidth = 70)


There were a small number of observations at TDWR stations in the 1990s, then none until NOAA began disseminating TDWR radar products. Even though there are a quarter as many TDWR stations as NEXRAD stations, the number of TDWR observations is far less than a quarter of the number of NEXRAD observations. Perhaps this is a result of the TDWR range being smaller than that of NEXRAD.

Station locations maps

Here is a map of the locations of the stations in the data.


leaflet() %>% 
  addProviderTiles("Stamen.TonerLite") %>% 
  addCircleMarkers(data = all_stations %>%
                     filter(state %in% state.abb, source == "NEXRAD"), 
                     lat = ~lat, lng = ~lon, label = ~name, group = "NEXRAD",
                   color = "purple", fillColor = "purple", radius = 6) %>%
  addCircleMarkers(data = all_stations %>%
                     filter(state %in% state.abb, source == "TDWR"), 
                   lat = ~lat, lng = ~lon, label = ~name, group = "TDWR", 
                   color = "green", fillColor = "green", radius = 6) %>%
  addCircleMarkers(data = all_stations %>%
                       filter(state %in% state.abb, source == "OTHER"), 
                     lat = ~lat, lng = ~lon, label = ~name, group = "OTHER", 
                   color = "orange", fillColor = "orange", radius = 6) %>%
  addLayersControl(
    overlayGroups = c("NEXRAD", "TDWR", "OTHER"),
    options = layersControlOptions(collapsed = FALSE)
  )


Observations far from stations

Earlier we noted a handful of seeming outlier observations that were several hundred miles away from the station. Let’s take a look at this for a couple of stations.

Omaha

The following map shows the location of the Omaha station, the convex hull of observations within 250 nautical miles of Omaha, and the locations of all points from the Omaha station greater than 250 nautical miles away. When you hover over a point, you see the time at which the observation occurred.

It may be notable that all of them appear to have occurred in the late 1990s and early 2000s.

omaha_points <- hail_full %>% 
  filter(wsr_id == "KOAX") 
 
omaha_hull <- omaha_points %>%
  filter(range <= 250) %>%
  select(lon, lat) %>%
  SpatialPoints %>%
  gConvexHull
 
omaha_far <- omaha_points %>% 
  filter(range > 250) %>%
  select(lon, lat, time) %>%
  mutate(time = as.character(time)) %>%
  SpatialPointsDataFrame(coords = select(., lon, lat), data = .)
leaflet() %>% 
  addProviderTiles("CartoDB.Positron") %>% 
  addPolygons(data = omaha_hull, fillOpacity = 0.2, weight = 1) %>%
  addCircleMarkers(data = omaha_far, color = "red", fillColor = "red", label = ~time) %>%
  addCircleMarkers(data = all_stations %>% 
                     filter(wsr_id == "KOAX") %>% 
                     SpatialPointsDataFrame(coords = select(., lon, lat), data = .),
                   color = "green", fillColor = "green", label = ~name)

Jacksonville

jax_points <- hail_full %>% 
  filter(wsr_id == "KJAX") 
 
jax_hull <- jax_points %>%
  filter(range <= 250) %>%
  select(lon, lat) %>%
  SpatialPoints %>%
  gConvexHull
 
jax_far <- jax_points %>% 
  filter(range > 250) %>%
  select(lon, lat, time) %>%
  mutate(time = as.character(time)) %>%
  SpatialPointsDataFrame(coords = select(., lon, lat), data = .)

The following map shows the same for Jacksonville as for Omaha above. Again, we note that all of the far outlying observations occurred in the late 1990s and early 2000s.


leaflet() %>% 
  addProviderTiles("CartoDB.Positron") %>% 
  addPolygons(data = jax_hull, fillOpacity = 0.2, weight = 1) %>%
  addCircleMarkers(data = jax_far, color = "red", fillColor = "red", label = ~time) %>%
  addCircleMarkers(data = all_stations %>% 
                     filter(wsr_id == "KJAX") %>% 
                     SpatialPointsDataFrame(coords = select(., lon, lat), data = .),
                   color = "green", fillColor = "green", label = ~name)


Grid of observation points

Also of note is that it looks like points can only occur only a grid determined by the azimuth and range. This becomes clear to see when we zoom in on some points. Here is a sample of points from Des Moines with a range from 150 to 200 and azimuth of 75 to 105. We can see points only occur on a grid.

dsm_points <- hail_full %>%
  filter(wsr_id == "KDMX")
 
set.seed(2642)
dsm_grid <- dsm_points %>%
  filter(range <= 200, range > 150, azimuth >= 75, azimuth <= 105) %>%
  sample_n(5000)
 
dsm_close <- dsm_points %>%
  filter(range < 10)


leaflet() %>%
  addProviderTiles("CartoDB.Positron") %>%
  addCircles(data = dsm_grid, lat = ~lat, lng = ~lon, radius = 2) %>%
  setView(lat = 41.7, lng = -90, zoom = 8)


It also is clear to see when we look at the following map of points less than 10 nautical miles from Des Moines.


leaflet() %>%
  addProviderTiles("CartoDB.Positron") %>%
  addCircles(data = dsm_close, lat = ~lat, lng = ~lon, radius = 2) %>%
  setView(lat = 41.73, lng = -93.72, zoom = 11)


Convex hulls of observations

The following map shows the convex hull of observations at each station (of observations with range less than or equal to 250 nautical miles, filtering out the seeming outliers). It visually drives home the point about how the range of the TDWR stations is much less than the NEXRAD stations. We also notice the considerable overlap in coverage across the country. This varies by region, with not as dense overlap in the mountain West and the heaviest overlap in the Midwest and South, but it exists across the country.

station_point_df <- hail_full %>%
  group_by(wsr_id) %>%
  by_slice(~ select(., lon, lat, range), .to = "pts")
 
cl <- makeCluster(24)
invisible(clusterEvalQ(cl, { library(sp); library(dplyr); library(rgeos) }))
 
station_point_df$chull <- parLapply(cl, station_point_df[["pts"]], function(pts) {
  pts %>%
    filter(range <= 250) %>%
    select(lon, lat) %>%
    SpatialPoints(proj4string = CRS("+proj=longlat +datum=NAD83 +no_defs +ellps=GRS80 +towgs84=0,0,0")) %>%
    gConvexHull
})
 
stopCluster(cl)
 
set.seed(25987)
station_colors <- all_stations %>% 
  select(wsr_id, source, name, lon, lat) %>%
  mutate(color = map_chr(
    source, 
    function(source) {
      if (source != "TDWR") color <- sample(c("red", "green", "blue", "purple", "brown"), 1)
      else color <- "black"
      color
    }
  ))
 
station_hulls <- station_point_df %>%
  left_join(station_colors, by = "wsr_id") %>%
  mutate(rownum = 1:nrow(.),
         hull_poly = pmap(
           list(chull, wsr_id, rownum, color, source), 
           function(hull, wsr_id, rownum, color, source) {
             hull@polygons[[1]]@ID <- as.character(rownum)
             df <- data_frame(wsr_id) %>%
               mutate(color = color, 
                      source = source)
             suppressWarnings({ rownames(df) <- rownum })
             SpatialPolygonsDataFrame(hull, df)
           }
         )
  )
 
station_hulls_combined <- lift_dl(sp::rbind.SpatialPolygonsDataFrame)(c(station_hulls$hull_poly))


leaflet() %>% 
  addProviderTiles("Stamen.TonerLite") %>% 
  addPolygons(data = station_hulls_combined[station_hulls_combined$source == "NEXRAD", ], group = "NEXRAD", 
              weight = 2, color = ~color, fillColor = ~color, fillOpacity = 0.05) %>%
  addPolygons(data = station_hulls_combined[station_hulls_combined$source == "OTHER", ], group = "OTHER", 
              weight = 2, color = ~color, fillColor = ~color, fillOpacity = 0.05) %>%
  addPolygons(data = station_hulls_combined[station_hulls_combined$source == "TDWR", ], group = "TDWR", 
              weight = 2, color = ~color, fillColor = ~color, fillOpacity = 0.35) %>%
  addLayersControl(
    overlayGroups = c("NEXRAD", "TDWR", "OTHER"),
    options = layersControlOptions(collapsed = FALSE)
  ) %>%
  setView(lng = -98, lat = 37, zoom = 4)


The following map reinforces the point about overlap in coverage. Shown on this map are stations for which the convex hull of their observations covers Des Moines. We observe that there are twelve stations whose convex hulls cover Des Moines, from Minneapolis to St. Louis.

dsm_hulls <- station_hulls_combined[
  as.logical(
    gIntersects(
      SpatialPoints(matrix(c(-93.72278, 41.73111), ncol = 2),
                    proj4string = CRS("+proj=longlat +datum=NAD83 +no_defs +ellps=GRS80 +towgs84=0,0,0")), 
      station_hulls_combined, byid = TRUE)
  ), ]


leaflet() %>% 
  addProviderTiles("Stamen.TonerLite") %>% 
  addPolygons(data = dsm_hulls,
              weight = 2, color = ~color, fillColor = ~color, fillOpacity = 0.05) %>%
  addCircleMarkers(data = semi_join(station_colors, dsm_hulls@data, by = "wsr_id"),
                   lat = ~lat, lng = ~lon, color = ~color, fillColor = ~color,
                   label = ~name)


Frequency of hail storm events in Illinois

As an initial look at the geographic of hail storm events in the NEXRAD, I looked at computing some basic observation counts in Illinois. Splitting the state of Illinois into a hexagonal grid, I computed the number of events in each bin.

To try to account for the widespread overlap in station coverage, and consequently getting observations from many different stations for the same event, what I did was for each hex bin, count the number of three-hour intervals containing an event.

For a specific example, if there was an event at 10:15 AM, that would count an event. Then the next observation in that bin at 1:15 PM that day or later would be the next event. That is, the next observation occurring at least three hours after the current active event would be counted as the next event for that point.

cppFunction(
  'NumericVector time_grp_cpp(NumericVector posix_secs, double boundary = 10800) {
    int n = posix_secs.size();
    NumericVector time_grp = NumericVector(n);
    time_grp[0] = 1;
    double offset = 0;
    for(int i = 1; i < n; i++) {
      offset = offset + posix_secs[i] - posix_secs[i - 1];
      if(offset > boundary) {
        time_grp[i] = time_grp[i - 1] + 1;
        offset = 0;
      } else {
        time_grp[i] = time_grp[i - 1];
      }
    }
    return time_grp;
  }'
)
 
state_shp <- readOGR("/data_shared/geospatial/shapefiles/cb_2015_us_state_20m/", 
                     "cb_2015_us_state_20m", verbose = FALSE)
 
states <- c("IL")
 
state_hull <- gConvexHull(state_shp[state_shp$STUSPS %in% states, ])
state_union <- gUnaryUnion(state_shp[state_shp$STUSPS %in% states, ])
 
state_hex <- state_hull %>%
  spsample(n = 2500, type = "hexagonal") %>%
  HexPoints2SpatialPolygons %>%
  `[`(as.logical(gIntersects(., state_union, byid = TRUE)), ) %>%
  SpatialPolygonsDataFrame(data = tibble(id = map_chr(.@polygons, ~ .@ID)), match.ID = FALSE)
 
state_hex_union <- gUnaryUnion(state_hex)
 
cores <- 32
 
hail_full_list <- hail_full %>%
  select(lon, lat, time, wsr_id) %>%
  split(rep(1:cores, each = ceiling(nrow(.) / cores))[1:nrow(.)])
 
cl <- makeCluster(cores)
invisible(clusterEvalQ(cl, { library(sp); library(rgeos); library(dplyr) }))
 
hail_points_by_hex <- parLapply(
  cl, hail_full_list, 
  function(hail_pts, state_hex, state_hex_union) {
    hail_pts_in_region <- hail_pts %>%
      SpatialPointsDataFrame(select(., lon, lat), data = ., 
                             proj4string = CRS(proj4string(state_hex))) %>%
      `[`(as.logical(gIntersects(., state_hex_union, byid = TRUE)), )
    hail_pts_in_region@data %>%
      mutate(id = suppressWarnings(sp::over(hail_pts_in_region, state_hex))[["id"]])
  },
  state_hex = state_hex, state_hex_union = state_hex_union
)
 
stopCluster(cl)
 
hail_points_hex_sorted <- hail_points_by_hex %>%
  bind_rows %>%
  arrange(id, time)
 
hail_hex_cnt <- hail_points_hex_sorted %>%
  group_by(id) %>%
  mutate(time_grp = time_grp_cpp(as.numeric(time))) %>%
  summarise(count = max(time_grp))
 
nexrad_list <- all_stations %>%
  filter(station_type == "NEXRAD") %>%
  .[["wsr_id"]]
 
hail_hex_cnt_nexrad <- hail_points_hex_sorted %>%
  filter(wsr_id %in% nexrad_list) %>%
  group_by(id) %>%
  mutate(time_grp = time_grp_cpp(as.numeric(time))) %>%
  summarise(count = max(time_grp))

The following shows the number of events by hex bin in the entire data for the state of Illinois.


state_hex_count <- state_hex
state_hex_count@data <- state_hex_count@data %>%
  left_join(hail_hex_cnt, by = "id")
 
color_pal <- colorNumeric("YlOrRd", state_hex_count$count)
 
leaflet(state_hex_count) %>% 
  addProviderTiles("Stamen.TonerLite") %>% 
  addPolygons(weight = 0.25, color = "black",
              fillColor = ~color_pal(count), fillOpacity = 0.7)%>%
  addLegend("bottomright", color_pal, state_hex_count$count, title = "Events", opacity = 1)


We can see a couple of concentrations around St. Louis and Chicago, which are locations of TDWR stations. Recalling the differences between NEXRAD and TDWR stations, I excluded TDWR observations and re-calculated the hex bin counts. We can see a bit of a difference from the previous map. The relative concentration around St. Louis has diminished (along with the concentration around Chicago to some degree), making the concentration in far southern Illinois relative to the rest of the state more pronounced.


state_hex_count_nexrad <- state_hex
state_hex_count_nexrad@data <- state_hex_count_nexrad@data %>%
  left_join(hail_hex_cnt_nexrad, by = "id")
  
color_pal <- colorNumeric("YlOrRd", state_hex_count_nexrad$count)
  
leaflet(state_hex_count_nexrad) %>% 
  addProviderTiles("Stamen.TonerLite") %>% 
  addPolygons(weight = 0.25, color = "black",
              fillColor = ~color_pal(count), fillOpacity = 0.7) %>%
  addLegend("bottomright", color_pal, state_hex_count_nexrad$count, title = "Events", opacity = 1)


LS0tCnRpdGxlOiAiTkVYUkFEIEhhaWwgRGF0YSIKYXV0aG9yOiAiTWF0dGhldyBWYW4gSGFsYSIKZGF0ZTogImByIGZvcm1hdChTeXMudGltZSgpLCAnJUEsICVCICVlLCAlWScpYCIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDoKICAgICAgY29sbGFwc2VkOiBmYWxzZQogICAgICBzbW9vdGhfc2Nyb2xsOiBmYWxzZQogICAgdGhlbWU6IHVuaXRlZAotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlID0gRkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldCh3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSkKb3B0aW9ucyhzY2lwZW4gPSAxMCkKYGBgCgpgYGB7cn0KbGlicmFyeShwYXJhbGxlbCkKbGlicmFyeShyZWFkcikKbGlicmFyeShkcGx5cikKbGlicmFyeShsdWJyaWRhdGUpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShwdXJycikKbGlicmFyeShSY3BwKQpsaWJyYXJ5KHJnZGFsKQpsaWJyYXJ5KHJnZW9zKQpsaWJyYXJ5KHNwKQpsaWJyYXJ5KHRpYmJsZSkKbGlicmFyeShsZWFmbGV0KQpsaWJyYXJ5KHN0cmluZ3IpCmxpYnJhcnkoeHRzKQpsaWJyYXJ5KHRpZHlyKQpsaWJyYXJ5KGR5Z3JhcGhzKQpsaWJyYXJ5KERUKQpgYGAKCiMgRGF0YQoKVGhlIE5PQUEgZGF0YSBJIGZvdW5kIG9uIEFXUyB3YXMgZm9yIFtORVhSQURdKGh0dHBzOi8vYXdzLmFtYXpvbi5jb20vbm9hYS1iaWctZGF0YS9uZXhyYWQvKSwgY29udGFpbmluZyBhbiBhcmNoaXZlIG9mIExldmVsIElJIGRhdGEgZnJvbSBKdW5lIDE5OTEgdG8gdGhlIHByZXNlbnQuIFRoZSBOT0FBIFtTZXZlcmUgV2VhdGhlciBEYXRhIEludmVudG9yeV0oaHR0cHM6Ly93d3cubmNkYy5ub2FhLmdvdi9zd2RpLyNJbnRybykgY29udGFpbnMgTGV2ZWwgSUlJIHByb2R1Y3QgZGF0YSBvbiBoYWlsIGFuZCBzdG9ybXMuCgpUbyBiZWdpbiwgSSBkb3dubG9hZGVkIHRoZSBORVhSQUQgaGFpbCBzaWduYXR1cmVzIFtkYXRhXShodHRwOi8vd3d3MS5uY2RjLm5vYWEuZ292L3B1Yi9kYXRhL3N3ZGkvZGF0YWJhc2UtY3N2L3YyLykuIFRoZXNlIGhhaWwgY3N2IGZpbGVzIHdlcmUgZG93bmxvYWRlZCBhbmQgZXh0cmFjdGVkIHRvIHRoZSBmb2xkZXIgYC9kYXRhX3NoYXJlZC9nZW9zcGF0aWFsL25leHJhZC9oYWlsL2NzdmAgb24gUlN0dWRpbyBTZXJ2ZXIuCgpgYGB7cn0KY2wgPC0gbWFrZUNsdXN0ZXIoNCkKaW52aXNpYmxlKGNsdXN0ZXJFdmFsUShjbCwgeyBsaWJyYXJ5KHJlYWRyKTsgbGlicmFyeShkcGx5cik7IGxpYnJhcnkobHVicmlkYXRlKSB9KSkKIApoYWlsX2RhdGEgPC0gY2x1c3RlckFwcGx5TEIoCiAgY2wsIGxpc3QuZmlsZXMoIi9kYXRhX3NoYXJlZC9nZW9zcGF0aWFsL25leHJhZC9oYWlsL2NzdiIsIGZ1bGwubmFtZXMgPSBUUlVFKSwKICBmdW5jdGlvbihwYXRoKSB7CiAgICByZWFkX2NzdihwYXRoLCBza2lwID0gMywgCiAgICAgICAgICAgICBjb2xfbmFtZXMgPSBjKCJ0aW1lIiwgImxvbiIsICJsYXQiLCAid3NyX2lkIiwgImNlbGxfaWQiLCAicmFuZ2UiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgImF6aW11dGgiLCAic2V2cHJvYiIsICJwcm9iIiwgIm1heHNpemUiKSwKICAgICAgICAgICAgIGNvbF90eXBlcyA9IGNvbHModGltZSA9IGNvbF9kYXRldGltZShmb3JtYXQgPSAiJVklbSVkJUglTSVTIikpKSAlPiUKICAgICAgbXV0YXRlKGF6aW11dGggPSBhemltdXRoICUlIDM2MCwKICAgICAgICAgICAgIHlyID0geWVhcih0aW1lKSwgCiAgICAgICAgICAgICBtb24gPSBtb250aCh0aW1lKSkKICB9CikKIApzdG9wQ2x1c3RlcihjbCkKIApoYWlsX2Z1bGwgPC0gYmluZF9yb3dzKGhhaWxfZGF0YSkKYGBgCgpIZXJlIGlzIGEgc2FtcGxlIG9mIGEgZmV3IHJvd3MgZnJvbSB0aGUgZGF0YS4KCjxicj4KCmBgYHtyfQpzZXQuc2VlZCgyNjA2KQogCmhhaWxfZnVsbCAlPiUKICBzYW1wbGVfbigxMDApICU+JQogIHNlbGVjdCgteXIsIC1tb24pICU+JQogIG11dGF0ZSh0aW1lID0gYXMuY2hhcmFjdGVyKHRpbWUpKSAlPiUKICBkYXRhdGFibGUocm93bmFtZXMgPSBGQUxTRSwKICAgICAgICAgICAgb3B0aW9ucyA9IGxpc3Qoc2Nyb2xsWCA9IFRSVUUpKQpgYGAKCjxicj4KClRoaXMgW3BhZ2VdKGh0dHBzOi8vd3d3Lm5jZGMubm9hYS5nb3Yvc3dkaXdzL2Nzdi9ueDNoYWlsKSBjb250YWlucyBzb21lIGRvY3VtZW50YXRpb24gb24gTkVYUkFEIExldmVsIElJSSBwcm9kdWN0cy4gVGhlIGZvbGxvd2luZyBhcmUgZGVmaW5pdGlvbnMgb2YgdGhlIHZhcmlhYmxlcyBpbiB0aGUgaGFpbCBkYXRhIHNldC4KCiogdGltZTogb2JzZXJ2YXRpb24gdGltZSAoR01UKQoKKiBsb246IGxvbmdpdHVkZQoKKiBsYXQ6IGxhdGl0dWRlCgoqIHdzcl9pZDogc2l0ZSBJRAoKKiBjZWxsX2lkOiBjZWxsIElEIHVuaXF1ZSB0byByYWRhciBzaXRlCgoqIHJhbmdlOiBkaXN0YW5jZSBmcm9tIHN0YXRpb24gKG5hdXRpY2FsIG1pbGVzKQoKKiBhemltdXRoOiBhbmdsZSBmcm9tIHN0YXRpb24gKGRlZ3JlZXMpCgoqIHNldnByb2I6IHByb2JhYmlsaXR5IG9mIHNldmVyZSBoYWlsIChwZXJjZW50KQoKKiBwcm9iOiBwcm9iYWJpbHR5IG9mIGhhaWwgKHBlcmNlbnQpCgoqIG1heF9zaXplOiBtYXggc2l6ZSAoaW5jaGVzKQoKVmFsdWVzIG9mIGAtOTk5YCBhcHBlYXIgdG8gYmUgbWlzc2luZyB2YWx1ZXMuCgoKIyBTdGF0aW9uIG1ldGFkYXRhCgpgYGB7cn0KbmV4cmFkX3N0YXRpb25zIDwtIHJlYWRfZndmKAogICJzdGF0aW9ucy9uZXhyYWQtc3RhdGlvbnMudHh0IiwgCiAgZndmX3Bvc2l0aW9ucyhzdGFydCA9IGMoMSwgMTAsIDE1LCAyMSwgNTIsIDczLCA3NiwgMTA3LCAxMTcsIDEyNywgMTM0LCAxNDApLCAKICAgICAgICAgICAgICAgIGVuZCA9IGMoOCwgMTMsIDE5LCA1MCwgNzEsIDc0LCAxMDUsIDExNSwgMTI1LCAxMzIsIDEzOCwgTkEpLAogICAgICAgICAgICAgICAgY29sX25hbWVzID0gYygibmNkY2lkIiwgImljYW8iLCAid2JhbiIsICJuYW1lIiwgImNvdW50cnkiLCAic3RhdGUiLCAiY291bnR5IiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJsYXQiLCAibG9uIiwgImVsZXZhdGlvbiIsICJ0eiIsICJzdGF0aW9uX3R5cGUiKSksIAogIHNraXAgPSAyLAogIGNvbF90eXBlcyA9ICJjY2NjY2NjZGRkZGMiCikgCiAKYXNvc19zdGF0aW9ucyA8LSByZWFkX2Z3ZigKICAic3RhdGlvbnMvYXNvcy1zdGF0aW9ucy50eHQiLAogIGZ3Zl9wb3NpdGlvbnMoc3RhcnQgPSBjKDEsIDEwLCAxNiwgMjMsIDI4LCA1OSwgOTAsIDExMSwgMTE0LCAxNDUsIDE1NSwgMTY2LCAxNzMsIDE3OSksCiAgICAgICAgICAgICAgICBlbmQgPSBjKDgsIDE0LCAyMSwgMjYsIDU3LCA4OCwgMTA5LCAxMTIsIDE0MywgMTUzLCAxNjQsIDE3MSwgMTc3LCBOQSksCiAgICAgICAgICAgICAgICBjb2xfbmFtZXMgPSBjKCJuY2RjaWQiLCAid2JhbiIsICJjb29waWQiLCAiY2FsbCIsICJuYW1lIiwgImFsdF9uYW1lIiwgImNvdW50cnkiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAic3RhdGUiLCAiY291bnR5IiwgImxhdCIsICJsb24iLCAiZWxldmF0aW9uIiwgInR6IiwgInN0YXRpb25fdHlwZSIpKSwKICBza2lwID0gMiwKICBjb2xfdHlwZXMgPSAiY2NjY2NjY2NjZGRkZGMiCikKIApzdGF0aW9uX2NudCA8LSBjb3VudChoYWlsX2Z1bGwsIHdzcl9pZCkKIApub25fbmV4cmFkIDwtIHN0YXRpb25fY250ICU+JQogIGFudGlfam9pbihuZXhyYWRfc3RhdGlvbnMsIGJ5ID0gYygid3NyX2lkIiA9ICJpY2FvIikpICU+JQogIG11dGF0ZShjYWxsID0gc3RyX3N1Yih3c3JfaWQsIHN0YXJ0ID0gMiksCiAgICAgICAgIGNhbGwgPSByZWNvZGUoY2FsbCwgIklDSCIgID0gIklDVCIsICJJRFMiID0gIklORCIsICJMVkUiID0gIkNMRSIpKQogCmhhaWxfbmV4cmFkIDwtIG5leHJhZF9zdGF0aW9ucyAlPiUgCiAgc2VtaV9qb2luKHN0YXRpb25fY250LCBieSA9IGMoImljYW8iID0gIndzcl9pZCIpKSAlPiUKICBzZWxlY3Qod3NyX2lkID0gaWNhbywgZXZlcnl0aGluZygpKSAlPiUKICBtdXRhdGUoc291cmNlID0gIk5FWFJBRCIpCiAKaGFpbF9hc29zIDwtIGFzb3Nfc3RhdGlvbnMgJT4lCiAgaW5uZXJfam9pbihub25fbmV4cmFkLCBieSA9ICJjYWxsIikgJT4lCiAgc2VsZWN0KHdzcl9pZCwgbmNkY2lkLCB3YmFuLCBuYW1lLCBjb3VudHJ5LCBzdGF0ZSwgY291bnR5LCBsYXQsIGxvbiwgZWxldmF0aW9uLCB0eiwgc3RhdGlvbl90eXBlKSAlPiUKICBtdXRhdGUoc291cmNlID0gIlREV1IiKQogCmFuZHJld3NfYWZiIDwtIHRpYmJsZSh3c3JfaWQgPSAiS0FEVyIsIG5jZGNpZCA9ICIyMDAwOTQ3MiIsIHdiYW4gPSAiMTM3MDUiLCAKICAgICAgICAgICAgICAgICAgICAgIG5hbWUgPSAiQ0FNUCBTUFJJTkdTIEFORFJFV1MgQUZCIiwgY291bnRyeSA9ICJVTklURUQgU1RBVEVTIiwgc3RhdGUgPSAiTUQiLAogICAgICAgICAgICAgICAgICAgICAgY291bnR5ID0gIlBSSU5DRSBHRU9SR0UnUyIsIGxhdCA9IDM4LjgxNjY3LCBsb24gPSAtNzYuODY2NjcsIGVsZXZhdGlvbiA9IDI4MiwgCiAgICAgICAgICAgICAgICAgICAgICB0eiA9IC01LCBzdGF0aW9uX3R5cGUgPSBOQV9jaGFyYWN0ZXJfLCBzb3VyY2UgPSAiT1RIRVIiKQogCmFsbF9zdGF0aW9ucyA8LSBiaW5kX3Jvd3MoaGFpbF9uZXhyYWQsIGhhaWxfYXNvcywgYW5kcmV3c19hZmIpCmBgYAoKVGhlIGhhaWwgZGF0YSBjb250YWlucyBvYnNlcnZhdGlvbnMgZnJvbSBib3RoIE5FWFJBRCBzdGF0aW9ucyBhbmQgVGVybWluYWwgRG9wcGxlciBXZWF0aGVyIFJhZGFyIChURFdSKSBzdGF0aW9ucy4KCkhlcmUgYXJlIHRoZSBvYnNlcnZhdGlvbiBjb3VudHMgZm9yIHRoZSBgciBucm93KHN0YXRpb25fY250KWAgc3RhdGlvbnMgYXBwZWFyaW5pbmcgaW4gdGhlIGRhdGEuCgo8YnI+CgpgYGB7cn0KZGF0YXRhYmxlKHN0YXRpb25fY250LAogICAgICAgICAgcm93bmFtZXMgPSBGQUxTRSwKICAgICAgICAgIGNvbG5hbWVzID0gYygiT2JzZXJ2YXRpb25zIiA9ICJuIiksCiAgICAgICAgICBvcHRpb25zID0gbGlzdChzY3JvbGxYID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgICAgIHBhZ2VMZW5ndGggPSA1LAogICAgICAgICAgICAgICAgICAgICAgICAgZG9tID0gImxpcHQiKSkgJT4lCiAgZm9ybWF0Q3VycmVuY3koIk9ic2VydmF0aW9ucyIsIGN1cnJlbmN5ID0gIiIsIGRpZ2l0cyA9IDApCmBgYAoKPGJyPgoKRnJvbSB0aGUgW0hpc3RvcmljYWwgT2JzZXJ2aW5nIE1ldGFkYXRhIFJlcG9zaXRvcnldKGh0dHA6Ly93d3cubmNkYy5ub2FhLmdvdi9ob21yLyksIEkgZG93bmxvYWRlZCBtZXRhZGF0YSBvbiB0aGUgTkVYUkFEIHN0YXRpb25zLiBUaGVyZSBhcmUgYHIgbnJvdyhuZXhyYWRfc3RhdGlvbnMpYCBORVhSQUQgc3RhdGlvbnMuIEZvciBhIGhhbmRmdWwgb2Ygc3RhdGlvbnMsIHNob3duIGluIHRoZSBmb2xsb3dpbmcgdGFibGUsIHRoZXJlIGV4aXN0ZWQgbm8gb2JzZXJ2YXRpb25zIGluIHRoZSBoYWlsIGRhdGEuCgo8YnI+CgpgYGB7cn0KbmV4cmFkX3N0YXRpb25zICU+JSAKICBhbnRpX2pvaW4oc3RhdGlvbl9jbnQsIGJ5ID0gYygiaWNhbyIgPSAid3NyX2lkIikpICU+JQogIGRhdGF0YWJsZShjbGFzcyA9ICJjZWxsLWJvcmRlciIsCiAgICAgICAgICAgIHJvd25hbWVzID0gRkFMU0UsCiAgICAgICAgICAgIG9wdGlvbnMgPSBsaXN0KHNjcm9sbFggPSBUUlVFLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgb3JkZXJpbmcgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgYXV0b1dpZHRoID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgZG9tID0gInQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICBwYWdlTGVuZ3RoID0gbnJvdyguKSkpCmBgYAoKPGJyPgoKRm9yIGByIG5yb3coaGFpbF9hc29zKWAgVERXUiBzdGF0aW9ucywgd2hpY2ggYXJlIGxvY2F0ZWQgYXQgYWlycG9ydHMsIEkgZm91bmQgbWV0YWRhdGEgaW5mb3JtYXRpb24gZnJvbSB0aGUgQXV0b21hdGVkIFN1cmZhY2UgT2JzZXJ2aW5nIFN5c3RlbSAoQVNPUykgc3RhdGlvbiBsaXN0IGF0IHRoZSBIaXN0b3JpY2FsIE9ic2VydmluZyBNZXRhZGF0YSBSZXBvc2l0b3J5LgoKRm9yIHRocmVlIHN0YXRpb25zLCBzaG93biBpbiB0aGUgZm9sbG93aW5nIHRhYmxlLCB0aGUgY2FsbCBzaWduICh3aGljaCBJIHVzZWQgdG8gam9pbiB0aGUgQVNPUyBzdGF0aW9ucyB0byB0aGUgaGFpbCBzdGF0aW9ucykgZGlmZmVyZWQgZnJvbSB0aGUgbGFzdCB0aHJlZSBsZXR0ZXJzIG9mIHRoZSBgd3NyX2lkYCB2YWx1ZS4KCjxicj4KCmBgYHtyfQp0cmliYmxlKH5jaXR5LCB+d3NyX2lkLCB+Y2FsbCwKICAgICAgICAiV2ljaGl0YSIsICJLSUNIIiwgIklDVCIsCiAgICAgICAgIkluZGlhbmFwb2xpcyIsICJLSURTIiwgIklORCIsCiAgICAgICAgIkNsZXZlbGFuZCIsICJLTFZFIiwgIkNMRSIpCmBgYAoKPGJyPgoKSSBwcmVzdW1lIHRoZSBgd3NyX2lkYCBmb3IgdGhlc2Ugc3RhdGlvbnMgZGlmZmVyZWQgYmVjYXVzZSB0aGVyZSBhbHJlYWR5IGV4aXN0ZWQgTkVYUkFEIHN0YXRpb25zIHdpdGggdGhlc2UgdmFsdWVzIChLSUNULCBLSU5ELCBLQ0xFKS4gQSBbbm90ZSByZWxlYXNlZF0oaHR0cDovL3d3dy5ud3Mubm9hYS5nb3Yvb3Mvbm90aWZpY2F0aW9uL3RpbjA4LTg1X3Rkd3Jfc3BnLnR4dCkgaW4gMjAwOCB3aGVuIE5PQUEgYmVnYW4gZGlzc2VtaW5hdGluZyBURFdSIHJhZGFyIHByb2R1Y3RzIHNlZW1zIHRvIGNvbmZpcm0gdGhhdCB0aGVzZSBjYWxsIHNpZ25zIGFuZCBpZCB2YWx1ZXMgY29ycmVzcG9uZCB3aXRoIGVhY2ggb3RoZXIuCgpJbiBhZGRpdGlvbiB0byB0aGUgYHIgbnJvdyhoYWlsX25leHJhZClgIE5FWFJBRCBzdGF0aW9ucyBhbmQgYHIgbnJvdyhoYWlsX2Fzb3MpYCBURFdSIHN0YXRpb25zLCB0aGVyZSB3YXMgb25lIGFkZGl0aW9uYWwgc3RhdGlvbiB3aXRoIG9ic2VydmF0aW9ucyBpbiB0aGUgaGFpbCBkYXRhIHRoYXQgd2FzIG5laXRoZXIgaW4gdGhlIE5FWFJBRCBvciBBU09TIGxpc3Q6IEtBRFcgYXQgW0FuZHJld3MgQUZCXShodHRwOi8vd3d3Lm5jZGMubm9hYS5nb3YvaG9tci8jbmNkY3N0bmlkPTIwMDA5NDcyJnRhYj1NU0hSKS4KCkhlcmUgaXMgYSB0YWJsZSBvZiBtZXRhZGF0YSBmb3IgYWxsIGByIG5yb3coYWxsX3N0YXRpb25zKWAgc3RhdGlvbnMgd2l0aCBvYnNlcnZhdGlvbnMgaW4gdGhlIGhhaWwgZGF0YS4KCjxicj4KCmBgYHtyfQphbGxfc3RhdGlvbnMgJT4lCiAgZGF0YXRhYmxlKHJvd25hbWVzID0gRkFMU0UsCiAgICAgICAgICAgIG9wdGlvbnMgPSBsaXN0KHNjcm9sbFggPSBUUlVFKSkKYGBgCgo8YnI+CgoKIyBCYXNpYyBkYXRhIGV4cGxvcmF0aW9uCgpgYGB7cn0KYXppbXV0aF9jbnQgPC0gaGFpbF9mdWxsICU+JQogIGNvdW50KGF6aW11dGgpCiAKcmFuZ2VfY250IDwtIGhhaWxfZnVsbCAlPiUKICBjb3VudChyYW5nZSkKYGBgCgpJIGRvd25sb2FkZWQgdGhlIGhhaWwgY3N2IGZpbGVzIGZyb20gMTk5NSB0byAyMDE1LiBUaGVyZSBhcmUgYSB0b3RhbCBvZiBgciBwcmV0dHlOdW0obnJvdyhoYWlsX2Z1bGwpLCBiaWcubWFyayA9ICIsIilgIG9ic2VydmF0aW9ucyBpbiB0aGUgZGF0YS4KCiMjIEF6aW11dGggZnJlcXVlbmN5CgpIZXJlIGlzIGEgZnJlcXVlbmN5IHBvbHlnb24gb2YgYGF6aW11dGhgIGluIHRoZSBkYXRhLgoKYGBge3J9CmdncGxvdChhemltdXRoX2NudCkgKyAKICBnZW9tX2ZyZXFwb2x5KGFlcyh4ID0gYXppbXV0aCwgeSA9IG4pLCBzdGF0ID0gImlkZW50aXR5IikgKwogIGV4cGFuZF9saW1pdHMoeSA9IDApICsgCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZShheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCkpCmBgYAoKCiMjIFJhbmdlIGZyZXF1ZW5jeQoKSGVyZSBpcyBhIGZyZXF1ZW5jeSBwb2x5Z29uIGZvciB0aGUgYHJhbmdlYCB2YXJpYWJsZS4KCmBgYHtyfQpnZ3Bsb3QocmFuZ2VfY250KSArIAogIGdlb21fZnJlcXBvbHkoYWVzKHggPSByYW5nZSwgeSA9IG4pLCBzdGF0ID0gImlkZW50aXR5IikgKwogIGV4cGFuZF9saW1pdHMoeSA9IDApICsgCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZShheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCkpCmBgYAoKV2Ugc2VlIHRoYXQgdGhlIG92ZXJ3aGVsbWluZyBtYWpvcml0eSBvZiByYW5nZXMgYXJlIGxlc3MgdGhhbiwgc2F5LCAyNTAgbmF1dGljYWwgbWlsZXMuIEhvd2V2ZXIsIHRoZXJlIGFyZSBhIGhhbmRmdWwgb2Ygc2VlbWluZ2x5IGV4dHJlbWUgb3V0bGllcnMuIFRoZSBtYXhpbXVtIHZhbHVlIG9mIGByYW5nZWAgaXMgYHIgcHJldHR5TnVtKG1heChyYW5nZV9jbnRbWyJyYW5nZSJdXSksIGJpZy5tYXJrID0gIiwiKWAsIGFuZCB0aGVyZSBhcmUgYHIgcHJldHR5TnVtKHN1bShmaWx0ZXIocmFuZ2VfY250LCByYW5nZSA+IDI1MClbWyJuIl1dKSwgYmlnLm1hcmsgPSAiLCIpYCBvYnNlcnZhdGlvbnMgd2l0aCBgcmFuZ2VgIGdyZWF0ZXIgdGhhbiAyNTAuIEhlcmUgaXMgYSBwbG90IG9mIHRoZSBmcmVxdWVuY3kgd2hlbiB3ZSBleGNsdWRlIHJhbmdlcyBncmVhdGVyIHRoYW4gMjUwLgoKYGBge3J9CmdncGxvdChyYW5nZV9jbnQgJT4lCiAgICAgICAgIGZpbHRlcihyYW5nZSA8PSAyNTApKSArIAogIGdlb21fZnJlcXBvbHkoYWVzKHggPSByYW5nZSwgeSA9IG4pLCBzdGF0ID0gImlkZW50aXR5IikgKwogIGV4cGFuZF9saW1pdHMoeSA9IDApICsgCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZShheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCkpCmBgYAoKVGhlIHBhdHRlcm4gaXMgZ2VuZXJhbGx5IHdoYXQgd2Ugd291bGQgZXhwZWN0LCB3aXRoIGFuIGluY3JlYXNlIGluIGZyZXF1ZW5jeSBhcyByYW5nZSBpbmNyZWFzZXMsIGFzIHRoZXJlIGlzIG1vcmUgYXZhaWxhYmxlIGFyZWEgZm9yIG9ic2VydmF0aW9ucywgdGhlbiBhIGRlY3JlYXNlIGFzIHJhbmdlIGNvbnRpbnVlcyB0byBpbmNyZWFzZSBiZXlvbmQgYSBjZXJ0YWluIHBvaW50LCBhcyBpdCBzdGFydHMgdG8gYmVjb21lIGxlc3MgbGlrZWx5IGZvciB0aGUgc3Rvcm0gdG8gYmUgZGV0ZWN0ZWQgYnkgdGhlIHJhZGFyIHN0YXRpb24uCgpUaGVyZSBhcmUgYSBjb3VwbGUgb2YgcGVjdWxpYXIgYmxpcHMgaW4gdGhlIGRhdGEsIHN1Y2ggYXMgdGhlIGxhcmdlIHNwaWtlIGF0IGFyb3VuZCAxMTAgbmF1dGljYWwgbWlsZXMuCgpgYGB7cn0KcmFuZ2VfY250X2J5X3NvdXJjZSA8LSBoYWlsX2Z1bGwgJT4lIAogIHNlbGVjdCh3c3JfaWQsIHJhbmdlKSAlPiUKICBsZWZ0X2pvaW4oYWxsX3N0YXRpb25zICU+JQogICAgICAgICAgICAgIHNlbGVjdCh3c3JfaWQsIHNvdXJjZSksCiAgICAgICAgICAgIGJ5ID0gIndzcl9pZCIpICU+JQogIGNvdW50KHNvdXJjZSwgcmFuZ2UpICU+JQogIHVuZ3JvdXAKIAphemltdXRoX2NudF9ieV9zb3VyY2UgPC0gaGFpbF9mdWxsICU+JSAKICBzZWxlY3Qod3NyX2lkLCBhemltdXRoKSAlPiUKICBsZWZ0X2pvaW4oYWxsX3N0YXRpb25zICU+JQogICAgICAgICAgICAgIHNlbGVjdCh3c3JfaWQsIHNvdXJjZSksCiAgICAgICAgICAgIGJ5ID0gIndzcl9pZCIpICU+JQogIGNvdW50KHNvdXJjZSwgYXppbXV0aCkgJT4lCiAgdW5ncm91cApgYGAKCkxldCdzIHRha2UgYSBsb29rIGF0IHRoZSBkaXN0cmlidXRpb24gb2YgcmFuZ2UgZm9yIE5FWFJBRCB2cy4gVERXUiBzdGF0aW9ucy4gT25jZSBhZ2Fpbiwgd2UnbGwgZmlsdGVyIHRvIHJhbmdlIG9mIGxlc3MgdGhhbiBvciBlcXVhbCB0byAyNTAgbmF1dGljYWwgbWlsZXMuCgpgYGB7ciwgZmlnLndpZHRoID0gOX0KZ2dwbG90KHJhbmdlX2NudF9ieV9zb3VyY2UgJT4lIAogICAgICAgICBmaWx0ZXIoc291cmNlICVpbiUgYygiTkVYUkFEIiwgIlREV1IiKSwgcmFuZ2UgPD0gMjUwKSAlPiUgCiAgICAgICAgIGdyb3VwX2J5KHNvdXJjZSkgJT4lIAogICAgICAgICBtdXRhdGUocmVsX2ZyZXEgPSBuIC8gc3VtKG4pKSAlPiUgCiAgICAgICAgIHVuZ3JvdXApICsgCiAgZ2VvbV9mcmVxcG9seShhZXMoeCA9IHJhbmdlLCB5ID0gcmVsX2ZyZXEpLCBzdGF0ID0gImlkZW50aXR5IikgKwogIGZhY2V0X3dyYXAofiBzb3VyY2UsIG5jb2wgPSAyKSArCiAgZXhwYW5kX2xpbWl0cyh5ID0gMCkgKyAKICB0aGVtZV9taW5pbWFsKCkgKwogIHRoZW1lKGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF9ibGFuaygpKQpgYGAKCldlIGNhbiBzZWUgdGhhdCB0aGUgVERXUiBzdGF0aW9ucyBoYXZlIGEgbXVjaCBtb3JlIGxpbWl0ZWQgcmFuZ2UgdGhhbiB0aGUgTkVYUkFEIHN0YXRpb25zLCB3aXRoIHRoZSBORVhSQUQgcmFuZ2UgYXBwZWFyaW5nIHRvIGZhbGwgb2ZmIGEgY2xpZmYgYXQgYXJvdW5kIDUwIG5hdXRpY2FsIG1pbGVzLgoKIyMgRnJlcXVlbmN5IG92ZXIgdGltZQoKYGBge3J9Cm1vbnRoX2NudCA8LSBoYWlsX2Z1bGwgJT4lIAogIGNvdW50KHdzcl9pZCwgeXIsIG1vbikgJT4lIAogIHVuZ3JvdXAgJT4lCiAgbXV0YXRlKHlybW9uID0gYXMueWVhcm1vbihwYXN0ZSh5ciwgc3RyX3BhZChtb24sIHdpZHRoID0gMiwgc2lkZSA9ICJsZWZ0IiwgcGFkID0gIjAiKSwgc2VwID0gIi0iKSkpCmBgYAoKVGhlIGZvbGxvd2luZyBjaGFydCBzaG93cyB0aGUgbnVtYmVyIG9mIG9ic2VydmF0aW9ucyBieSBtb250aCBmb3IgdGhlIGR1cmF0aW9uIG9mIHRoZSBkYXRhIHNldC4KCjxicj4KCmBgYHtyLCBmaWcud2lkdGggPSAxMX0KbW9udGhfY250ICU+JSAKICBncm91cF9ieSh5cm1vbikgJT4lIAogIHRhbGx5KG4pICU+JQogIHsgeHRzKHNlbGVjdCguLCBubiksIG9yZGVyLmJ5ID0gLltbInlybW9uIl1dKSB9ICU+JQogIGR5Z3JhcGggJT4lCiAgZHlPcHRpb25zKG1heE51bWJlcldpZHRoID0gMjApICU+JQogIGR5U2VyaWVzKCJubiIsIGxhYmVsID0gIk9ic2VydmF0aW9ucyIpICU+JQogIGR5QXhpcygieSIsIHBpeGVsc1BlckxhYmVsID0gMTAwLAogICAgICAgICB2YWx1ZUZvcm1hdHRlciA9ICdmdW5jdGlvbihkKXtyZXR1cm4gZC50b1N0cmluZygpLnJlcGxhY2UoL1xcQig/PShcXGR7M30pKyg/IVxcZCkpL2csICIsIik7fScsCiAgICAgICAgIGF4aXNMYWJlbEZvcm1hdHRlciA9ICdmdW5jdGlvbihkKXtyZXR1cm4gZC50b1N0cmluZygpLnJlcGxhY2UoL1xcQig/PShcXGR7M30pKyg/IVxcZCkpL2csICIsIik7fScsCiAgICAgICAgIGF4aXNMYWJlbFdpZHRoID0gNzApCmBgYAoKPGJyPgoKV2Ugc2VlIGFuIGluY3JlYXNlIGluIHRoIGVhcmx5IHRvIG1pZCAyMDAwcywgcGVyaGFwcyBhcyBtb3JlIHN0YXRpb25zIGJlY2FtZSBhY3RpdmUuIFdlIGFsc28gbm90aWNlIHRoZSBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zIHdhcyBxdWl0ZSBkZXByZXNzZWQgaW4gMjAwMi4KCkhlcmUgaXMgYSBjaGFydCBvZiB0aGUgYnJlYWtkb3duIGluIG9ic2VydmF0aW9uIGNvdW50cyBiZXR3ZWVuIE5FWFJBRCBhbmQgVERXUiBzdGF0aW9ucy4KCjxicj4KCmBgYHtyLCBmaWcud2lkdGggPSAxMX0KbW9udGhfY250ICU+JSAKICBsZWZ0X2pvaW4oc2VsZWN0KGFsbF9zdGF0aW9ucywgd3NyX2lkLCBzb3VyY2UpLCBieSA9ICJ3c3JfaWQiKSAlPiUKICBncm91cF9ieShzb3VyY2UsIHlybW9uKSAlPiUgCiAgdGFsbHkobikgJT4lCiAgdW5ncm91cCAlPiUKICBzcHJlYWQoc291cmNlLCBubikgJT4lCiAgeyB4dHMoc2VsZWN0KC4sIE5FWFJBRCwgVERXUiksIG9yZGVyLmJ5ID0gLltbInlybW9uIl1dKSB9ICU+JQogIGR5Z3JhcGggJT4lCiAgZHlPcHRpb25zKG1heE51bWJlcldpZHRoID0gMjApICU+JQogIGR5QXhpcygieSIsIHBpeGVsc1BlckxhYmVsID0gMTAwLAogICAgICAgICB2YWx1ZUZvcm1hdHRlciA9ICdmdW5jdGlvbihkKXtyZXR1cm4gZC50b1N0cmluZygpLnJlcGxhY2UoL1xcQig/PShcXGR7M30pKyg/IVxcZCkpL2csICIsIik7fScsCiAgICAgICAgIGF4aXNMYWJlbEZvcm1hdHRlciA9ICdmdW5jdGlvbihkKXtyZXR1cm4gZC50b1N0cmluZygpLnJlcGxhY2UoL1xcQig/PShcXGR7M30pKyg/IVxcZCkpL2csICIsIik7fScsCiAgICAgICAgIGF4aXNMYWJlbFdpZHRoID0gNzApCmBgYAoKPGJyPgoKVGhlcmUgd2VyZSBhIHNtYWxsIG51bWJlciBvZiBvYnNlcnZhdGlvbnMgYXQgVERXUiBzdGF0aW9ucyBpbiB0aGUgMTk5MHMsIHRoZW4gbm9uZSB1bnRpbCBOT0FBIGJlZ2FuIGRpc3NlbWluYXRpbmcgVERXUiByYWRhciBwcm9kdWN0cy4gRXZlbiB0aG91Z2ggdGhlcmUgYXJlIGEgcXVhcnRlciBhcyBtYW55IFREV1Igc3RhdGlvbnMgYXMgTkVYUkFEIHN0YXRpb25zLCB0aGUgbnVtYmVyIG9mIFREV1Igb2JzZXJ2YXRpb25zIGlzIGZhciBsZXNzIHRoYW4gYSBxdWFydGVyIG9mIHRoZSBudW1iZXIgb2YgTkVYUkFEIG9ic2VydmF0aW9ucy4gUGVyaGFwcyB0aGlzIGlzIGEgcmVzdWx0IG9mIHRoZSBURFdSIHJhbmdlIGJlaW5nIHNtYWxsZXIgdGhhbiB0aGF0IG9mIE5FWFJBRC4KCiMgU3RhdGlvbiBsb2NhdGlvbnMgbWFwcwoKSGVyZSBpcyBhIG1hcCBvZiB0aGUgbG9jYXRpb25zIG9mIHRoZSBzdGF0aW9ucyBpbiB0aGUgZGF0YS4KCjxicj4KCmBgYHtyLCBmaWcud2lkdGggPSAxMSwgZmlnLmhlaWdodCA9IDd9CmxlYWZsZXQoKSAlPiUgCiAgYWRkUHJvdmlkZXJUaWxlcygiU3RhbWVuLlRvbmVyTGl0ZSIpICU+JSAKICBhZGRDaXJjbGVNYXJrZXJzKGRhdGEgPSBhbGxfc3RhdGlvbnMgJT4lCiAgICAgICAgICAgICAgICAgICAgIGZpbHRlcihzdGF0ZSAlaW4lIHN0YXRlLmFiYiwgc291cmNlID09ICJORVhSQUQiKSwgCiAgICAgICAgICAgICAgICAgICAgIGxhdCA9IH5sYXQsIGxuZyA9IH5sb24sIGxhYmVsID0gfm5hbWUsIGdyb3VwID0gIk5FWFJBRCIsCiAgICAgICAgICAgICAgICAgICBjb2xvciA9ICJwdXJwbGUiLCBmaWxsQ29sb3IgPSAicHVycGxlIiwgcmFkaXVzID0gNikgJT4lCiAgYWRkQ2lyY2xlTWFya2VycyhkYXRhID0gYWxsX3N0YXRpb25zICU+JQogICAgICAgICAgICAgICAgICAgICBmaWx0ZXIoc3RhdGUgJWluJSBzdGF0ZS5hYmIsIHNvdXJjZSA9PSAiVERXUiIpLCAKICAgICAgICAgICAgICAgICAgIGxhdCA9IH5sYXQsIGxuZyA9IH5sb24sIGxhYmVsID0gfm5hbWUsIGdyb3VwID0gIlREV1IiLCAKICAgICAgICAgICAgICAgICAgIGNvbG9yID0gImdyZWVuIiwgZmlsbENvbG9yID0gImdyZWVuIiwgcmFkaXVzID0gNikgJT4lCiAgYWRkQ2lyY2xlTWFya2VycyhkYXRhID0gYWxsX3N0YXRpb25zICU+JQogICAgICAgICAgICAgICAgICAgICAgIGZpbHRlcihzdGF0ZSAlaW4lIHN0YXRlLmFiYiwgc291cmNlID09ICJPVEhFUiIpLCAKICAgICAgICAgICAgICAgICAgICAgbGF0ID0gfmxhdCwgbG5nID0gfmxvbiwgbGFiZWwgPSB+bmFtZSwgZ3JvdXAgPSAiT1RIRVIiLCAKICAgICAgICAgICAgICAgICAgIGNvbG9yID0gIm9yYW5nZSIsIGZpbGxDb2xvciA9ICJvcmFuZ2UiLCByYWRpdXMgPSA2KSAlPiUKICBhZGRMYXllcnNDb250cm9sKAogICAgb3ZlcmxheUdyb3VwcyA9IGMoIk5FWFJBRCIsICJURFdSIiwgIk9USEVSIiksCiAgICBvcHRpb25zID0gbGF5ZXJzQ29udHJvbE9wdGlvbnMoY29sbGFwc2VkID0gRkFMU0UpCiAgKQpgYGAKCjxicj4KCiMjIE9ic2VydmF0aW9ucyBmYXIgZnJvbSBzdGF0aW9ucwoKRWFybGllciB3ZSBub3RlZCBhIGhhbmRmdWwgb2Ygc2VlbWluZyBvdXRsaWVyIG9ic2VydmF0aW9ucyB0aGF0IHdlcmUgc2V2ZXJhbCBodW5kcmVkIG1pbGVzIGF3YXkgZnJvbSB0aGUgc3RhdGlvbi4gTGV0J3MgdGFrZSBhIGxvb2sgYXQgdGhpcyBmb3IgYSBjb3VwbGUgb2Ygc3RhdGlvbnMuCgojIyMgT21haGEKClRoZSBmb2xsb3dpbmcgbWFwIHNob3dzIHRoZSBsb2NhdGlvbiBvZiB0aGUgT21haGEgc3RhdGlvbiwgdGhlIGNvbnZleCBodWxsIG9mIG9ic2VydmF0aW9ucyB3aXRoaW4gMjUwIG5hdXRpY2FsIG1pbGVzIG9mIE9tYWhhLCBhbmQgdGhlIGxvY2F0aW9ucyBvZiBhbGwgcG9pbnRzIGZyb20gdGhlIE9tYWhhIHN0YXRpb24gZ3JlYXRlciB0aGFuIDI1MCBuYXV0aWNhbCBtaWxlcyBhd2F5LiBXaGVuIHlvdSBob3ZlciBvdmVyIGEgcG9pbnQsIHlvdSBzZWUgdGhlIHRpbWUgYXQgd2hpY2ggdGhlIG9ic2VydmF0aW9uIG9jY3VycmVkLgoKSXQgbWF5IGJlIG5vdGFibGUgdGhhdCBhbGwgb2YgdGhlbSBhcHBlYXIgdG8gaGF2ZSBvY2N1cnJlZCBpbiB0aGUgbGF0ZSAxOTkwcyBhbmQgZWFybHkgMjAwMHMuCgpgYGB7cn0Kb21haGFfcG9pbnRzIDwtIGhhaWxfZnVsbCAlPiUgCiAgZmlsdGVyKHdzcl9pZCA9PSAiS09BWCIpIAogCm9tYWhhX2h1bGwgPC0gb21haGFfcG9pbnRzICU+JQogIGZpbHRlcihyYW5nZSA8PSAyNTApICU+JQogIHNlbGVjdChsb24sIGxhdCkgJT4lCiAgU3BhdGlhbFBvaW50cyAlPiUKICBnQ29udmV4SHVsbAogCm9tYWhhX2ZhciA8LSBvbWFoYV9wb2ludHMgJT4lIAogIGZpbHRlcihyYW5nZSA+IDI1MCkgJT4lCiAgc2VsZWN0KGxvbiwgbGF0LCB0aW1lKSAlPiUKICBtdXRhdGUodGltZSA9IGFzLmNoYXJhY3Rlcih0aW1lKSkgJT4lCiAgU3BhdGlhbFBvaW50c0RhdGFGcmFtZShjb29yZHMgPSBzZWxlY3QoLiwgbG9uLCBsYXQpLCBkYXRhID0gLikKYGBgCgpgYGB7ciwgZmlnLndpZHRoID0gMTF9CmxlYWZsZXQoKSAlPiUgCiAgYWRkUHJvdmlkZXJUaWxlcygiQ2FydG9EQi5Qb3NpdHJvbiIpICU+JSAKICBhZGRQb2x5Z29ucyhkYXRhID0gb21haGFfaHVsbCwgZmlsbE9wYWNpdHkgPSAwLjIsIHdlaWdodCA9IDEpICU+JQogIGFkZENpcmNsZU1hcmtlcnMoZGF0YSA9IG9tYWhhX2ZhciwgY29sb3IgPSAicmVkIiwgZmlsbENvbG9yID0gInJlZCIsIGxhYmVsID0gfnRpbWUpICU+JQogIGFkZENpcmNsZU1hcmtlcnMoZGF0YSA9IGFsbF9zdGF0aW9ucyAlPiUgCiAgICAgICAgICAgICAgICAgICAgIGZpbHRlcih3c3JfaWQgPT0gIktPQVgiKSAlPiUgCiAgICAgICAgICAgICAgICAgICAgIFNwYXRpYWxQb2ludHNEYXRhRnJhbWUoY29vcmRzID0gc2VsZWN0KC4sIGxvbiwgbGF0KSwgZGF0YSA9IC4pLAogICAgICAgICAgICAgICAgICAgY29sb3IgPSAiZ3JlZW4iLCBmaWxsQ29sb3IgPSAiZ3JlZW4iLCBsYWJlbCA9IH5uYW1lKQpgYGAKCgojIyMgSmFja3NvbnZpbGxlCgpgYGB7cn0KamF4X3BvaW50cyA8LSBoYWlsX2Z1bGwgJT4lIAogIGZpbHRlcih3c3JfaWQgPT0gIktKQVgiKSAKIApqYXhfaHVsbCA8LSBqYXhfcG9pbnRzICU+JQogIGZpbHRlcihyYW5nZSA8PSAyNTApICU+JQogIHNlbGVjdChsb24sIGxhdCkgJT4lCiAgU3BhdGlhbFBvaW50cyAlPiUKICBnQ29udmV4SHVsbAogCmpheF9mYXIgPC0gamF4X3BvaW50cyAlPiUgCiAgZmlsdGVyKHJhbmdlID4gMjUwKSAlPiUKICBzZWxlY3QobG9uLCBsYXQsIHRpbWUpICU+JQogIG11dGF0ZSh0aW1lID0gYXMuY2hhcmFjdGVyKHRpbWUpKSAlPiUKICBTcGF0aWFsUG9pbnRzRGF0YUZyYW1lKGNvb3JkcyA9IHNlbGVjdCguLCBsb24sIGxhdCksIGRhdGEgPSAuKQpgYGAKClRoZSBmb2xsb3dpbmcgbWFwIHNob3dzIHRoZSBzYW1lIGZvciBKYWNrc29udmlsbGUgYXMgZm9yIE9tYWhhIGFib3ZlLiBBZ2Fpbiwgd2Ugbm90ZSB0aGF0IGFsbCBvZiB0aGUgZmFyIG91dGx5aW5nIG9ic2VydmF0aW9ucyBvY2N1cnJlZCBpbiB0aGUgbGF0ZSAxOTkwcyBhbmQgZWFybHkgMjAwMHMuCgo8YnI+CgpgYGB7ciwgZmlnLndpZHRoID0gMTF9CmxlYWZsZXQoKSAlPiUgCiAgYWRkUHJvdmlkZXJUaWxlcygiQ2FydG9EQi5Qb3NpdHJvbiIpICU+JSAKICBhZGRQb2x5Z29ucyhkYXRhID0gamF4X2h1bGwsIGZpbGxPcGFjaXR5ID0gMC4yLCB3ZWlnaHQgPSAxKSAlPiUKICBhZGRDaXJjbGVNYXJrZXJzKGRhdGEgPSBqYXhfZmFyLCBjb2xvciA9ICJyZWQiLCBmaWxsQ29sb3IgPSAicmVkIiwgbGFiZWwgPSB+dGltZSkgJT4lCiAgYWRkQ2lyY2xlTWFya2VycyhkYXRhID0gYWxsX3N0YXRpb25zICU+JSAKICAgICAgICAgICAgICAgICAgICAgZmlsdGVyKHdzcl9pZCA9PSAiS0pBWCIpICU+JSAKICAgICAgICAgICAgICAgICAgICAgU3BhdGlhbFBvaW50c0RhdGFGcmFtZShjb29yZHMgPSBzZWxlY3QoLiwgbG9uLCBsYXQpLCBkYXRhID0gLiksCiAgICAgICAgICAgICAgICAgICBjb2xvciA9ICJncmVlbiIsIGZpbGxDb2xvciA9ICJncmVlbiIsIGxhYmVsID0gfm5hbWUpCmBgYAoKPGJyPgoKCiMjIEdyaWQgb2Ygb2JzZXJ2YXRpb24gcG9pbnRzCgpBbHNvIG9mIG5vdGUgaXMgdGhhdCBpdCBsb29rcyBsaWtlIHBvaW50cyBjYW4gb25seSBvY2N1ciBvbmx5IGEgZ3JpZCBkZXRlcm1pbmVkIGJ5IHRoZSBhemltdXRoIGFuZCByYW5nZS4gVGhpcyBiZWNvbWVzIGNsZWFyIHRvIHNlZSB3aGVuIHdlIHpvb20gaW4gb24gc29tZSBwb2ludHMuIEhlcmUgaXMgYSBzYW1wbGUgb2YgcG9pbnRzIGZyb20gRGVzIE1vaW5lcyB3aXRoIGEgcmFuZ2UgZnJvbSAxNTAgdG8gMjAwIGFuZCBhemltdXRoIG9mIDc1IHRvIDEwNS4gV2UgY2FuIHNlZSBwb2ludHMgb25seSBvY2N1ciBvbiBhIGdyaWQuCgpgYGB7cn0KZHNtX3BvaW50cyA8LSBoYWlsX2Z1bGwgJT4lCiAgZmlsdGVyKHdzcl9pZCA9PSAiS0RNWCIpCiAKc2V0LnNlZWQoMjY0MikKZHNtX2dyaWQgPC0gZHNtX3BvaW50cyAlPiUKICBmaWx0ZXIocmFuZ2UgPD0gMjAwLCByYW5nZSA+IDE1MCwgYXppbXV0aCA+PSA3NSwgYXppbXV0aCA8PSAxMDUpICU+JQogIHNhbXBsZV9uKDUwMDApCiAKZHNtX2Nsb3NlIDwtIGRzbV9wb2ludHMgJT4lCiAgZmlsdGVyKHJhbmdlIDwgMTApCmBgYAoKPGJyPgoKYGBge3IsIGZpZy53aWR0aCA9IDExfQpsZWFmbGV0KCkgJT4lCiAgYWRkUHJvdmlkZXJUaWxlcygiQ2FydG9EQi5Qb3NpdHJvbiIpICU+JQogIGFkZENpcmNsZXMoZGF0YSA9IGRzbV9ncmlkLCBsYXQgPSB+bGF0LCBsbmcgPSB+bG9uLCByYWRpdXMgPSAyKSAlPiUKICBzZXRWaWV3KGxhdCA9IDQxLjcsIGxuZyA9IC05MCwgem9vbSA9IDgpCmBgYAoKPGJyPgoKSXQgYWxzbyBpcyBjbGVhciB0byBzZWUgd2hlbiB3ZSBsb29rIGF0IHRoZSBmb2xsb3dpbmcgbWFwIG9mIHBvaW50cyBsZXNzIHRoYW4gMTAgbmF1dGljYWwgbWlsZXMgZnJvbSBEZXMgTW9pbmVzLgoKPGJyPgoKYGBge3IgZmlnLndpZHRoID0gMTF9CmxlYWZsZXQoKSAlPiUKICBhZGRQcm92aWRlclRpbGVzKCJDYXJ0b0RCLlBvc2l0cm9uIikgJT4lCiAgYWRkQ2lyY2xlcyhkYXRhID0gZHNtX2Nsb3NlLCBsYXQgPSB+bGF0LCBsbmcgPSB+bG9uLCByYWRpdXMgPSAyKSAlPiUKICBzZXRWaWV3KGxhdCA9IDQxLjczLCBsbmcgPSAtOTMuNzIsIHpvb20gPSAxMSkKYGBgCgo8YnI+CgojIyBDb252ZXggaHVsbHMgb2Ygb2JzZXJ2YXRpb25zCgpUaGUgZm9sbG93aW5nIG1hcCBzaG93cyB0aGUgY29udmV4IGh1bGwgb2Ygb2JzZXJ2YXRpb25zIGF0IGVhY2ggc3RhdGlvbiAob2Ygb2JzZXJ2YXRpb25zIHdpdGggcmFuZ2UgbGVzcyB0aGFuIG9yIGVxdWFsIHRvIDI1MCBuYXV0aWNhbCBtaWxlcywgZmlsdGVyaW5nIG91dCB0aGUgc2VlbWluZyBvdXRsaWVycykuIEl0IHZpc3VhbGx5IGRyaXZlcyBob21lIHRoZSBwb2ludCBhYm91dCBob3cgdGhlIHJhbmdlIG9mIHRoZSBURFdSIHN0YXRpb25zIGlzIG11Y2ggbGVzcyB0aGFuIHRoZSBORVhSQUQgc3RhdGlvbnMuIFdlIGFsc28gbm90aWNlIHRoZSBjb25zaWRlcmFibGUgb3ZlcmxhcCBpbiBjb3ZlcmFnZSBhY3Jvc3MgdGhlIGNvdW50cnkuIFRoaXMgdmFyaWVzIGJ5IHJlZ2lvbiwgd2l0aCBub3QgYXMgZGVuc2Ugb3ZlcmxhcCBpbiB0aGUgbW91bnRhaW4gV2VzdCBhbmQgdGhlIGhlYXZpZXN0IG92ZXJsYXAgaW4gdGhlIE1pZHdlc3QgYW5kIFNvdXRoLCBidXQgaXQgZXhpc3RzIGFjcm9zcyB0aGUgY291bnRyeS4KCmBgYHtyfQpzdGF0aW9uX3BvaW50X2RmIDwtIGhhaWxfZnVsbCAlPiUKICBncm91cF9ieSh3c3JfaWQpICU+JQogIGJ5X3NsaWNlKH4gc2VsZWN0KC4sIGxvbiwgbGF0LCByYW5nZSksIC50byA9ICJwdHMiKQogCmNsIDwtIG1ha2VDbHVzdGVyKDI0KQppbnZpc2libGUoY2x1c3RlckV2YWxRKGNsLCB7IGxpYnJhcnkoc3ApOyBsaWJyYXJ5KGRwbHlyKTsgbGlicmFyeShyZ2VvcykgfSkpCiAKc3RhdGlvbl9wb2ludF9kZiRjaHVsbCA8LSBwYXJMYXBwbHkoY2wsIHN0YXRpb25fcG9pbnRfZGZbWyJwdHMiXV0sIGZ1bmN0aW9uKHB0cykgewogIHB0cyAlPiUKICAgIGZpbHRlcihyYW5nZSA8PSAyNTApICU+JQogICAgc2VsZWN0KGxvbiwgbGF0KSAlPiUKICAgIFNwYXRpYWxQb2ludHMocHJvajRzdHJpbmcgPSBDUlMoIitwcm9qPWxvbmdsYXQgK2RhdHVtPU5BRDgzICtub19kZWZzICtlbGxwcz1HUlM4MCArdG93Z3M4ND0wLDAsMCIpKSAlPiUKICAgIGdDb252ZXhIdWxsCn0pCiAKc3RvcENsdXN0ZXIoY2wpCiAKc2V0LnNlZWQoMjU5ODcpCnN0YXRpb25fY29sb3JzIDwtIGFsbF9zdGF0aW9ucyAlPiUgCiAgc2VsZWN0KHdzcl9pZCwgc291cmNlLCBuYW1lLCBsb24sIGxhdCkgJT4lCiAgbXV0YXRlKGNvbG9yID0gbWFwX2NocigKICAgIHNvdXJjZSwgCiAgICBmdW5jdGlvbihzb3VyY2UpIHsKICAgICAgaWYgKHNvdXJjZSAhPSAiVERXUiIpIGNvbG9yIDwtIHNhbXBsZShjKCJyZWQiLCAiZ3JlZW4iLCAiYmx1ZSIsICJwdXJwbGUiLCAiYnJvd24iKSwgMSkKICAgICAgZWxzZSBjb2xvciA8LSAiYmxhY2siCiAgICAgIGNvbG9yCiAgICB9CiAgKSkKIApzdGF0aW9uX2h1bGxzIDwtIHN0YXRpb25fcG9pbnRfZGYgJT4lCiAgbGVmdF9qb2luKHN0YXRpb25fY29sb3JzLCBieSA9ICJ3c3JfaWQiKSAlPiUKICBtdXRhdGUocm93bnVtID0gMTpucm93KC4pLAogICAgICAgICBodWxsX3BvbHkgPSBwbWFwKAogICAgICAgICAgIGxpc3QoY2h1bGwsIHdzcl9pZCwgcm93bnVtLCBjb2xvciwgc291cmNlKSwgCiAgICAgICAgICAgZnVuY3Rpb24oaHVsbCwgd3NyX2lkLCByb3dudW0sIGNvbG9yLCBzb3VyY2UpIHsKICAgICAgICAgICAgIGh1bGxAcG9seWdvbnNbWzFdXUBJRCA8LSBhcy5jaGFyYWN0ZXIocm93bnVtKQogICAgICAgICAgICAgZGYgPC0gZGF0YV9mcmFtZSh3c3JfaWQpICU+JQogICAgICAgICAgICAgICBtdXRhdGUoY29sb3IgPSBjb2xvciwgCiAgICAgICAgICAgICAgICAgICAgICBzb3VyY2UgPSBzb3VyY2UpCiAgICAgICAgICAgICBzdXBwcmVzc1dhcm5pbmdzKHsgcm93bmFtZXMoZGYpIDwtIHJvd251bSB9KQogICAgICAgICAgICAgU3BhdGlhbFBvbHlnb25zRGF0YUZyYW1lKGh1bGwsIGRmKQogICAgICAgICAgIH0KICAgICAgICAgKQogICkKIApzdGF0aW9uX2h1bGxzX2NvbWJpbmVkIDwtIGxpZnRfZGwoc3A6OnJiaW5kLlNwYXRpYWxQb2x5Z29uc0RhdGFGcmFtZSkoYyhzdGF0aW9uX2h1bGxzJGh1bGxfcG9seSkpCmBgYAoKCjxicj4KCmBgYHtyLCBmaWcud2lkdGggPSAxMX0KbGVhZmxldCgpICU+JSAKICBhZGRQcm92aWRlclRpbGVzKCJTdGFtZW4uVG9uZXJMaXRlIikgJT4lIAogIGFkZFBvbHlnb25zKGRhdGEgPSBzdGF0aW9uX2h1bGxzX2NvbWJpbmVkW3N0YXRpb25faHVsbHNfY29tYmluZWQkc291cmNlID09ICJORVhSQUQiLCBdLCBncm91cCA9ICJORVhSQUQiLCAKICAgICAgICAgICAgICB3ZWlnaHQgPSAyLCBjb2xvciA9IH5jb2xvciwgZmlsbENvbG9yID0gfmNvbG9yLCBmaWxsT3BhY2l0eSA9IDAuMDUpICU+JQogIGFkZFBvbHlnb25zKGRhdGEgPSBzdGF0aW9uX2h1bGxzX2NvbWJpbmVkW3N0YXRpb25faHVsbHNfY29tYmluZWQkc291cmNlID09ICJPVEhFUiIsIF0sIGdyb3VwID0gIk9USEVSIiwgCiAgICAgICAgICAgICAgd2VpZ2h0ID0gMiwgY29sb3IgPSB+Y29sb3IsIGZpbGxDb2xvciA9IH5jb2xvciwgZmlsbE9wYWNpdHkgPSAwLjA1KSAlPiUKICBhZGRQb2x5Z29ucyhkYXRhID0gc3RhdGlvbl9odWxsc19jb21iaW5lZFtzdGF0aW9uX2h1bGxzX2NvbWJpbmVkJHNvdXJjZSA9PSAiVERXUiIsIF0sIGdyb3VwID0gIlREV1IiLCAKICAgICAgICAgICAgICB3ZWlnaHQgPSAyLCBjb2xvciA9IH5jb2xvciwgZmlsbENvbG9yID0gfmNvbG9yLCBmaWxsT3BhY2l0eSA9IDAuMzUpICU+JQogIGFkZExheWVyc0NvbnRyb2woCiAgICBvdmVybGF5R3JvdXBzID0gYygiTkVYUkFEIiwgIlREV1IiLCAiT1RIRVIiKSwKICAgIG9wdGlvbnMgPSBsYXllcnNDb250cm9sT3B0aW9ucyhjb2xsYXBzZWQgPSBGQUxTRSkKICApICU+JQogIHNldFZpZXcobG5nID0gLTk4LCBsYXQgPSAzNywgem9vbSA9IDQpCmBgYAoKPGJyPgoKVGhlIGZvbGxvd2luZyBtYXAgcmVpbmZvcmNlcyB0aGUgcG9pbnQgYWJvdXQgb3ZlcmxhcCBpbiBjb3ZlcmFnZS4gU2hvd24gb24gdGhpcyBtYXAgYXJlIHN0YXRpb25zIGZvciB3aGljaCB0aGUgY29udmV4IGh1bGwgb2YgdGhlaXIgb2JzZXJ2YXRpb25zIGNvdmVycyBEZXMgTW9pbmVzLiBXZSBvYnNlcnZlIHRoYXQgdGhlcmUgYXJlIHR3ZWx2ZSBzdGF0aW9ucyB3aG9zZSBjb252ZXggaHVsbHMgY292ZXIgRGVzIE1vaW5lcywgZnJvbSBNaW5uZWFwb2xpcyB0byBTdC4gTG91aXMuCgpgYGB7cn0KZHNtX2h1bGxzIDwtIHN0YXRpb25faHVsbHNfY29tYmluZWRbCiAgYXMubG9naWNhbCgKICAgIGdJbnRlcnNlY3RzKAogICAgICBTcGF0aWFsUG9pbnRzKG1hdHJpeChjKC05My43MjI3OCwgNDEuNzMxMTEpLCBuY29sID0gMiksCiAgICAgICAgICAgICAgICAgICAgcHJvajRzdHJpbmcgPSBDUlMoIitwcm9qPWxvbmdsYXQgK2RhdHVtPU5BRDgzICtub19kZWZzICtlbGxwcz1HUlM4MCArdG93Z3M4ND0wLDAsMCIpKSwgCiAgICAgIHN0YXRpb25faHVsbHNfY29tYmluZWQsIGJ5aWQgPSBUUlVFKQogICksIF0KYGBgCgo8YnI+CgpgYGB7ciwgZmlnLndpZHRoID0gMTF9CmxlYWZsZXQoKSAlPiUgCiAgYWRkUHJvdmlkZXJUaWxlcygiU3RhbWVuLlRvbmVyTGl0ZSIpICU+JSAKICBhZGRQb2x5Z29ucyhkYXRhID0gZHNtX2h1bGxzLAogICAgICAgICAgICAgIHdlaWdodCA9IDIsIGNvbG9yID0gfmNvbG9yLCBmaWxsQ29sb3IgPSB+Y29sb3IsIGZpbGxPcGFjaXR5ID0gMC4wNSkgJT4lCiAgYWRkQ2lyY2xlTWFya2VycyhkYXRhID0gc2VtaV9qb2luKHN0YXRpb25fY29sb3JzLCBkc21faHVsbHNAZGF0YSwgYnkgPSAid3NyX2lkIiksCiAgICAgICAgICAgICAgICAgICBsYXQgPSB+bGF0LCBsbmcgPSB+bG9uLCBjb2xvciA9IH5jb2xvciwgZmlsbENvbG9yID0gfmNvbG9yLAogICAgICAgICAgICAgICAgICAgbGFiZWwgPSB+bmFtZSkKYGBgCgo8YnI+CgoKIyBGcmVxdWVuY3kgb2YgaGFpbCBzdG9ybSBldmVudHMgaW4gSWxsaW5vaXMKCkFzIGFuIGluaXRpYWwgbG9vayBhdCB0aGUgZ2VvZ3JhcGhpYyBvZiBoYWlsIHN0b3JtIGV2ZW50cyBpbiB0aGUgTkVYUkFELCBJIGxvb2tlZCBhdCBjb21wdXRpbmcgc29tZSBiYXNpYyBvYnNlcnZhdGlvbiBjb3VudHMgaW4gSWxsaW5vaXMuIFNwbGl0dGluZyB0aGUgc3RhdGUgb2YgSWxsaW5vaXMgaW50byBhIGhleGFnb25hbCBncmlkLCBJIGNvbXB1dGVkIHRoZSBudW1iZXIgb2YgZXZlbnRzIGluIGVhY2ggYmluLiAKClRvIHRyeSB0byBhY2NvdW50IGZvciB0aGUgd2lkZXNwcmVhZCBvdmVybGFwIGluIHN0YXRpb24gY292ZXJhZ2UsIGFuZCBjb25zZXF1ZW50bHkgZ2V0dGluZyBvYnNlcnZhdGlvbnMgZnJvbSBtYW55IGRpZmZlcmVudCBzdGF0aW9ucyBmb3IgdGhlIHNhbWUgZXZlbnQsIHdoYXQgSSBkaWQgd2FzIGZvciBlYWNoIGhleCBiaW4sIGNvdW50IHRoZSBudW1iZXIgb2YgdGhyZWUtaG91ciBpbnRlcnZhbHMgY29udGFpbmluZyBhbiBldmVudC4gCgpGb3IgYSBzcGVjaWZpYyBleGFtcGxlLCBpZiB0aGVyZSB3YXMgYW4gZXZlbnQgYXQgMTA6MTUgQU0sIHRoYXQgd291bGQgY291bnQgYW4gZXZlbnQuIFRoZW4gdGhlIG5leHQgb2JzZXJ2YXRpb24gaW4gdGhhdCBiaW4gYXQgMToxNSBQTSB0aGF0IGRheSBvciBsYXRlciB3b3VsZCBiZSB0aGUgbmV4dCBldmVudC4gVGhhdCBpcywgdGhlIG5leHQgb2JzZXJ2YXRpb24gb2NjdXJyaW5nIGF0IGxlYXN0IHRocmVlIGhvdXJzIGFmdGVyIHRoZSBjdXJyZW50IGFjdGl2ZSBldmVudCB3b3VsZCBiZSBjb3VudGVkIGFzIHRoZSBuZXh0IGV2ZW50IGZvciB0aGF0IHBvaW50LgoKYGBge3J9CmNwcEZ1bmN0aW9uKAogICdOdW1lcmljVmVjdG9yIHRpbWVfZ3JwX2NwcChOdW1lcmljVmVjdG9yIHBvc2l4X3NlY3MsIGRvdWJsZSBib3VuZGFyeSA9IDEwODAwKSB7CiAgICBpbnQgbiA9IHBvc2l4X3NlY3Muc2l6ZSgpOwogICAgTnVtZXJpY1ZlY3RvciB0aW1lX2dycCA9IE51bWVyaWNWZWN0b3Iobik7CiAgICB0aW1lX2dycFswXSA9IDE7CiAgICBkb3VibGUgb2Zmc2V0ID0gMDsKICAgIGZvcihpbnQgaSA9IDE7IGkgPCBuOyBpKyspIHsKICAgICAgb2Zmc2V0ID0gb2Zmc2V0ICsgcG9zaXhfc2Vjc1tpXSAtIHBvc2l4X3NlY3NbaSAtIDFdOwogICAgICBpZihvZmZzZXQgPiBib3VuZGFyeSkgewogICAgICAgIHRpbWVfZ3JwW2ldID0gdGltZV9ncnBbaSAtIDFdICsgMTsKICAgICAgICBvZmZzZXQgPSAwOwogICAgICB9IGVsc2UgewogICAgICAgIHRpbWVfZ3JwW2ldID0gdGltZV9ncnBbaSAtIDFdOwogICAgICB9CiAgICB9CiAgICByZXR1cm4gdGltZV9ncnA7CiAgfScKKQogCnN0YXRlX3NocCA8LSByZWFkT0dSKCIvZGF0YV9zaGFyZWQvZ2Vvc3BhdGlhbC9zaGFwZWZpbGVzL2NiXzIwMTVfdXNfc3RhdGVfMjBtLyIsIAogICAgICAgICAgICAgICAgICAgICAiY2JfMjAxNV91c19zdGF0ZV8yMG0iLCB2ZXJib3NlID0gRkFMU0UpCiAKc3RhdGVzIDwtIGMoIklMIikKIApzdGF0ZV9odWxsIDwtIGdDb252ZXhIdWxsKHN0YXRlX3NocFtzdGF0ZV9zaHAkU1RVU1BTICVpbiUgc3RhdGVzLCBdKQpzdGF0ZV91bmlvbiA8LSBnVW5hcnlVbmlvbihzdGF0ZV9zaHBbc3RhdGVfc2hwJFNUVVNQUyAlaW4lIHN0YXRlcywgXSkKIApzdGF0ZV9oZXggPC0gc3RhdGVfaHVsbCAlPiUKICBzcHNhbXBsZShuID0gMjUwMCwgdHlwZSA9ICJoZXhhZ29uYWwiKSAlPiUKICBIZXhQb2ludHMyU3BhdGlhbFBvbHlnb25zICU+JQogIGBbYChhcy5sb2dpY2FsKGdJbnRlcnNlY3RzKC4sIHN0YXRlX3VuaW9uLCBieWlkID0gVFJVRSkpLCApICU+JQogIFNwYXRpYWxQb2x5Z29uc0RhdGFGcmFtZShkYXRhID0gdGliYmxlKGlkID0gbWFwX2NociguQHBvbHlnb25zLCB+IC5ASUQpKSwgbWF0Y2guSUQgPSBGQUxTRSkKIApzdGF0ZV9oZXhfdW5pb24gPC0gZ1VuYXJ5VW5pb24oc3RhdGVfaGV4KQogCmNvcmVzIDwtIDMyCiAKaGFpbF9mdWxsX2xpc3QgPC0gaGFpbF9mdWxsICU+JQogIHNlbGVjdChsb24sIGxhdCwgdGltZSwgd3NyX2lkKSAlPiUKICBzcGxpdChyZXAoMTpjb3JlcywgZWFjaCA9IGNlaWxpbmcobnJvdyguKSAvIGNvcmVzKSlbMTpucm93KC4pXSkKIApjbCA8LSBtYWtlQ2x1c3Rlcihjb3JlcykKaW52aXNpYmxlKGNsdXN0ZXJFdmFsUShjbCwgeyBsaWJyYXJ5KHNwKTsgbGlicmFyeShyZ2Vvcyk7IGxpYnJhcnkoZHBseXIpIH0pKQogCmhhaWxfcG9pbnRzX2J5X2hleCA8LSBwYXJMYXBwbHkoCiAgY2wsIGhhaWxfZnVsbF9saXN0LCAKICBmdW5jdGlvbihoYWlsX3B0cywgc3RhdGVfaGV4LCBzdGF0ZV9oZXhfdW5pb24pIHsKICAgIGhhaWxfcHRzX2luX3JlZ2lvbiA8LSBoYWlsX3B0cyAlPiUKICAgICAgU3BhdGlhbFBvaW50c0RhdGFGcmFtZShzZWxlY3QoLiwgbG9uLCBsYXQpLCBkYXRhID0gLiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvajRzdHJpbmcgPSBDUlMocHJvajRzdHJpbmcoc3RhdGVfaGV4KSkpICU+JQogICAgICBgW2AoYXMubG9naWNhbChnSW50ZXJzZWN0cyguLCBzdGF0ZV9oZXhfdW5pb24sIGJ5aWQgPSBUUlVFKSksICkKICAgIGhhaWxfcHRzX2luX3JlZ2lvbkBkYXRhICU+JQogICAgICBtdXRhdGUoaWQgPSBzdXBwcmVzc1dhcm5pbmdzKHNwOjpvdmVyKGhhaWxfcHRzX2luX3JlZ2lvbiwgc3RhdGVfaGV4KSlbWyJpZCJdXSkKICB9LAogIHN0YXRlX2hleCA9IHN0YXRlX2hleCwgc3RhdGVfaGV4X3VuaW9uID0gc3RhdGVfaGV4X3VuaW9uCikKIApzdG9wQ2x1c3RlcihjbCkKIApoYWlsX3BvaW50c19oZXhfc29ydGVkIDwtIGhhaWxfcG9pbnRzX2J5X2hleCAlPiUKICBiaW5kX3Jvd3MgJT4lCiAgYXJyYW5nZShpZCwgdGltZSkKIApoYWlsX2hleF9jbnQgPC0gaGFpbF9wb2ludHNfaGV4X3NvcnRlZCAlPiUKICBncm91cF9ieShpZCkgJT4lCiAgbXV0YXRlKHRpbWVfZ3JwID0gdGltZV9ncnBfY3BwKGFzLm51bWVyaWModGltZSkpKSAlPiUKICBzdW1tYXJpc2UoY291bnQgPSBtYXgodGltZV9ncnApKQogCm5leHJhZF9saXN0IDwtIGFsbF9zdGF0aW9ucyAlPiUKICBmaWx0ZXIoc3RhdGlvbl90eXBlID09ICJORVhSQUQiKSAlPiUKICAuW1sid3NyX2lkIl1dCiAKaGFpbF9oZXhfY250X25leHJhZCA8LSBoYWlsX3BvaW50c19oZXhfc29ydGVkICU+JQogIGZpbHRlcih3c3JfaWQgJWluJSBuZXhyYWRfbGlzdCkgJT4lCiAgZ3JvdXBfYnkoaWQpICU+JQogIG11dGF0ZSh0aW1lX2dycCA9IHRpbWVfZ3JwX2NwcChhcy5udW1lcmljKHRpbWUpKSkgJT4lCiAgc3VtbWFyaXNlKGNvdW50ID0gbWF4KHRpbWVfZ3JwKSkKYGBgCgoKVGhlIGZvbGxvd2luZyBzaG93cyB0aGUgbnVtYmVyIG9mIGV2ZW50cyBieSBoZXggYmluIGluIHRoZSBlbnRpcmUgZGF0YSBmb3IgdGhlIHN0YXRlIG9mIElsbGlub2lzLgoKPGJyPgoKYGBge3IsIGZpZy5oZWlnaHQgPSAxMSwgZmlnLndpZHRoID0gOX0Kc3RhdGVfaGV4X2NvdW50IDwtIHN0YXRlX2hleApzdGF0ZV9oZXhfY291bnRAZGF0YSA8LSBzdGF0ZV9oZXhfY291bnRAZGF0YSAlPiUKICBsZWZ0X2pvaW4oaGFpbF9oZXhfY250LCBieSA9ICJpZCIpCiAKY29sb3JfcGFsIDwtIGNvbG9yTnVtZXJpYygiWWxPclJkIiwgc3RhdGVfaGV4X2NvdW50JGNvdW50KQogCmxlYWZsZXQoc3RhdGVfaGV4X2NvdW50KSAlPiUgCiAgYWRkUHJvdmlkZXJUaWxlcygiU3RhbWVuLlRvbmVyTGl0ZSIpICU+JSAKICBhZGRQb2x5Z29ucyh3ZWlnaHQgPSAwLjI1LCBjb2xvciA9ICJibGFjayIsCiAgICAgICAgICAgICAgZmlsbENvbG9yID0gfmNvbG9yX3BhbChjb3VudCksIGZpbGxPcGFjaXR5ID0gMC43KSU+JQogIGFkZExlZ2VuZCgiYm90dG9tcmlnaHQiLCBjb2xvcl9wYWwsIHN0YXRlX2hleF9jb3VudCRjb3VudCwgdGl0bGUgPSAiRXZlbnRzIiwgb3BhY2l0eSA9IDEpCgpgYGAKCjxicj4KCldlIGNhbiBzZWUgYSBjb3VwbGUgb2YgY29uY2VudHJhdGlvbnMgYXJvdW5kIFN0LiBMb3VpcyBhbmQgQ2hpY2Fnbywgd2hpY2ggYXJlIGxvY2F0aW9ucyBvZiBURFdSIHN0YXRpb25zLiBSZWNhbGxpbmcgdGhlIGRpZmZlcmVuY2VzIGJldHdlZW4gTkVYUkFEIGFuZCBURFdSIHN0YXRpb25zLCBJIGV4Y2x1ZGVkIFREV1Igb2JzZXJ2YXRpb25zIGFuZCByZS1jYWxjdWxhdGVkIHRoZSBoZXggYmluIGNvdW50cy4gV2UgY2FuIHNlZSBhIGJpdCBvZiBhIGRpZmZlcmVuY2UgZnJvbSB0aGUgcHJldmlvdXMgbWFwLiBUaGUgcmVsYXRpdmUgY29uY2VudHJhdGlvbiBhcm91bmQgU3QuIExvdWlzIGhhcyBkaW1pbmlzaGVkIChhbG9uZyB3aXRoIHRoZSBjb25jZW50cmF0aW9uIGFyb3VuZCBDaGljYWdvIHRvIHNvbWUgZGVncmVlKSwgbWFraW5nIHRoZSBjb25jZW50cmF0aW9uIGluIGZhciBzb3V0aGVybiBJbGxpbm9pcyByZWxhdGl2ZSB0byB0aGUgcmVzdCBvZiB0aGUgc3RhdGUgbW9yZSBwcm9ub3VuY2VkLgoKPGJyPgoKYGBge3IsIGZpZy5oZWlnaHQgPSAxMSwgZmlnLndpZHRoID0gOX0Kc3RhdGVfaGV4X2NvdW50X25leHJhZCA8LSBzdGF0ZV9oZXgKc3RhdGVfaGV4X2NvdW50X25leHJhZEBkYXRhIDwtIHN0YXRlX2hleF9jb3VudF9uZXhyYWRAZGF0YSAlPiUKICBsZWZ0X2pvaW4oaGFpbF9oZXhfY250X25leHJhZCwgYnkgPSAiaWQiKQogIApjb2xvcl9wYWwgPC0gY29sb3JOdW1lcmljKCJZbE9yUmQiLCBzdGF0ZV9oZXhfY291bnRfbmV4cmFkJGNvdW50KQogIApsZWFmbGV0KHN0YXRlX2hleF9jb3VudF9uZXhyYWQpICU+JSAKICBhZGRQcm92aWRlclRpbGVzKCJTdGFtZW4uVG9uZXJMaXRlIikgJT4lIAogIGFkZFBvbHlnb25zKHdlaWdodCA9IDAuMjUsIGNvbG9yID0gImJsYWNrIiwKICAgICAgICAgICAgICBmaWxsQ29sb3IgPSB+Y29sb3JfcGFsKGNvdW50KSwgZmlsbE9wYWNpdHkgPSAwLjcpICU+JQogIGFkZExlZ2VuZCgiYm90dG9tcmlnaHQiLCBjb2xvcl9wYWwsIHN0YXRlX2hleF9jb3VudF9uZXhyYWQkY291bnQsIHRpdGxlID0gIkV2ZW50cyIsIG9wYWNpdHkgPSAxKQpgYGAKCjxicj4KCgoK